[FIR] Fix K2 behavior according to RULES1

The compiler should only report diagnostics for
comparisons over builtins and identity-less types,
other incompatibilities should be reported
via inspections.
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
index 4badab0..2e89a3e 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
@@ -3410,12 +3410,26 @@
             token,
         )
     }
+    add(FirErrors.SENSELESS_COMPARISON_ERROR) { firDiagnostic ->
+        SenselessComparisonErrorImpl(
+            firDiagnostic.a.source!!.psi as KtExpression,
+            firDiagnostic.b,
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
     add(FirErrors.SENSELESS_NULL_IN_WHEN) { firDiagnostic ->
         SenselessNullInWhenImpl(
             firDiagnostic as KtPsiDiagnostic,
             token,
         )
     }
+    add(FirErrors.SENSELESS_NULL_IN_WHEN_ERROR) { firDiagnostic ->
+        SenselessNullInWhenErrorImpl(
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
     add(FirErrors.TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM) { firDiagnostic ->
         TypecheckerHasRunIntoRecursiveProblemImpl(
             firDiagnostic as KtPsiDiagnostic,
@@ -3804,6 +3818,30 @@
             token,
         )
     }
+    add(FirErrors.INCOMPATIBLE_ENUM_COMPARISON) { firDiagnostic ->
+        IncompatibleEnumComparisonImpl(
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
+    add(FirErrors.FORBIDDEN_IDENTITY_EQUALS) { firDiagnostic ->
+        ForbiddenIdentityEqualsImpl(
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
+    add(FirErrors.FORBIDDEN_IDENTITY_EQUALS_WARNING) { firDiagnostic ->
+        ForbiddenIdentityEqualsWarningImpl(
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
+            firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
     add(FirErrors.INC_DEC_SHOULD_NOT_RETURN_UNIT) { firDiagnostic ->
         IncDecShouldNotReturnUnitImpl(
             firDiagnostic as KtPsiDiagnostic,
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
index 5226d8c..873ebc9 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
@@ -2384,10 +2384,20 @@
         abstract val compareResult: Boolean
     }
 
+    abstract class SenselessComparisonError : KtFirDiagnostic<KtExpression>() {
+        override val diagnosticClass get() = SenselessComparisonError::class
+        abstract val expression: KtExpression
+        abstract val compareResult: Boolean
+    }
+
     abstract class SenselessNullInWhen : KtFirDiagnostic<KtElement>() {
         override val diagnosticClass get() = SenselessNullInWhen::class
     }
 
+    abstract class SenselessNullInWhenError : KtFirDiagnostic<KtElement>() {
+        override val diagnosticClass get() = SenselessNullInWhenError::class
+    }
+
     abstract class TypecheckerHasRunIntoRecursiveProblem : KtFirDiagnostic<KtExpression>() {
         override val diagnosticClass get() = TypecheckerHasRunIntoRecursiveProblem::class
     }
@@ -2654,6 +2664,24 @@
         abstract val rightType: KtType
     }
 
+    abstract class IncompatibleEnumComparison : KtFirDiagnostic<KtElement>() {
+        override val diagnosticClass get() = IncompatibleEnumComparison::class
+        abstract val leftType: KtType
+        abstract val rightType: KtType
+    }
+
+    abstract class ForbiddenIdentityEquals : KtFirDiagnostic<KtElement>() {
+        override val diagnosticClass get() = ForbiddenIdentityEquals::class
+        abstract val leftType: KtType
+        abstract val rightType: KtType
+    }
+
+    abstract class ForbiddenIdentityEqualsWarning : KtFirDiagnostic<KtElement>() {
+        override val diagnosticClass get() = ForbiddenIdentityEqualsWarning::class
+        abstract val leftType: KtType
+        abstract val rightType: KtType
+    }
+
     abstract class IncDecShouldNotReturnUnit : KtFirDiagnostic<KtExpression>() {
         override val diagnosticClass get() = IncDecShouldNotReturnUnit::class
     }
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
index c42a25e..3e8742f 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
@@ -2874,11 +2874,23 @@
     override val token: KtLifetimeToken,
 ) : KtFirDiagnostic.SenselessComparison(), KtAbstractFirDiagnostic<KtExpression>
 
+internal class SenselessComparisonErrorImpl(
+    override val expression: KtExpression,
+    override val compareResult: Boolean,
+    override val firDiagnostic: KtPsiDiagnostic,
+    override val token: KtLifetimeToken,
+) : KtFirDiagnostic.SenselessComparisonError(), KtAbstractFirDiagnostic<KtExpression>
+
 internal class SenselessNullInWhenImpl(
     override val firDiagnostic: KtPsiDiagnostic,
     override val token: KtLifetimeToken,
 ) : KtFirDiagnostic.SenselessNullInWhen(), KtAbstractFirDiagnostic<KtElement>
 
+internal class SenselessNullInWhenErrorImpl(
+    override val firDiagnostic: KtPsiDiagnostic,
+    override val token: KtLifetimeToken,
+) : KtFirDiagnostic.SenselessNullInWhenError(), KtAbstractFirDiagnostic<KtElement>
+
 internal class TypecheckerHasRunIntoRecursiveProblemImpl(
     override val firDiagnostic: KtPsiDiagnostic,
     override val token: KtLifetimeToken,
@@ -3200,6 +3212,27 @@
     override val token: KtLifetimeToken,
 ) : KtFirDiagnostic.IncompatibleEnumComparisonError(), KtAbstractFirDiagnostic<KtElement>
 
+internal class IncompatibleEnumComparisonImpl(
+    override val leftType: KtType,
+    override val rightType: KtType,
+    override val firDiagnostic: KtPsiDiagnostic,
+    override val token: KtLifetimeToken,
+) : KtFirDiagnostic.IncompatibleEnumComparison(), KtAbstractFirDiagnostic<KtElement>
+
+internal class ForbiddenIdentityEqualsImpl(
+    override val leftType: KtType,
+    override val rightType: KtType,
+    override val firDiagnostic: KtPsiDiagnostic,
+    override val token: KtLifetimeToken,
+) : KtFirDiagnostic.ForbiddenIdentityEquals(), KtAbstractFirDiagnostic<KtElement>
+
+internal class ForbiddenIdentityEqualsWarningImpl(
+    override val leftType: KtType,
+    override val rightType: KtType,
+    override val firDiagnostic: KtPsiDiagnostic,
+    override val token: KtLifetimeToken,
+) : KtFirDiagnostic.ForbiddenIdentityEqualsWarning(), KtAbstractFirDiagnostic<KtElement>
+
 internal class IncDecShouldNotReturnUnitImpl(
     override val firDiagnostic: KtPsiDiagnostic,
     override val token: KtLifetimeToken,
diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
index 51b150c..ef10dea 100644
--- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
+++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
@@ -1172,7 +1172,12 @@
             parameter<FirExpression>("expression")
             parameter<Boolean>("compareResult")
         }
+        val SENSELESS_COMPARISON_ERROR by warning<KtExpression> {
+            parameter<FirExpression>("expression")
+            parameter<Boolean>("compareResult")
+        }
         val SENSELESS_NULL_IN_WHEN by warning<KtElement>()
+        val SENSELESS_NULL_IN_WHEN_ERROR by warning<KtElement>()
         val TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM by error<KtExpression>()
     }
 
@@ -1321,6 +1326,18 @@
             parameter<ConeKotlinType>("leftType")
             parameter<ConeKotlinType>("rightType")
         }
+        val INCOMPATIBLE_ENUM_COMPARISON by error<KtElement> {
+            parameter<ConeKotlinType>("leftType")
+            parameter<ConeKotlinType>("rightType")
+        }
+        val FORBIDDEN_IDENTITY_EQUALS by error<KtElement> {
+            parameter<ConeKotlinType>("leftType")
+            parameter<ConeKotlinType>("rightType")
+        }
+        val FORBIDDEN_IDENTITY_EQUALS_WARNING by error<KtElement> {
+            parameter<ConeKotlinType>("leftType")
+            parameter<ConeKotlinType>("rightType")
+        }
         val INC_DEC_SHOULD_NOT_RETURN_UNIT by error<KtExpression>(PositioningStrategy.OPERATOR)
         val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error<KtExpression>(PositioningStrategy.OPERATOR) {
             parameter<FirNamedFunctionSymbol>("functionSymbol")
diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
index 8946292..599577e 100644
--- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
+++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
@@ -619,7 +619,9 @@
     val INITIALIZATION_BEFORE_DECLARATION by error1<KtExpression, FirBasedSymbol<*>>()
     val UNREACHABLE_CODE by warning2<KtElement, Set<KtSourceElement>, Set<KtSourceElement>>(SourceElementPositioningStrategies.UNREACHABLE_CODE)
     val SENSELESS_COMPARISON by warning2<KtExpression, FirExpression, Boolean>()
+    val SENSELESS_COMPARISON_ERROR by warning2<KtExpression, FirExpression, Boolean>()
     val SENSELESS_NULL_IN_WHEN by warning0<KtElement>()
+    val SENSELESS_NULL_IN_WHEN_ERROR by warning0<KtElement>()
     val TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM by error0<KtExpression>()
 
     // Nullability
@@ -686,6 +688,9 @@
     val EQUALITY_NOT_APPLICABLE by error3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
     val EQUALITY_NOT_APPLICABLE_WARNING by warning3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
     val INCOMPATIBLE_ENUM_COMPARISON_ERROR by error2<KtElement, ConeKotlinType, ConeKotlinType>()
+    val INCOMPATIBLE_ENUM_COMPARISON by error2<KtElement, ConeKotlinType, ConeKotlinType>()
+    val FORBIDDEN_IDENTITY_EQUALS by error2<KtElement, ConeKotlinType, ConeKotlinType>()
+    val FORBIDDEN_IDENTITY_EQUALS_WARNING by error2<KtElement, ConeKotlinType, ConeKotlinType>()
     val INC_DEC_SHOULD_NOT_RETURN_UNIT by error0<KtExpression>(SourceElementPositioningStrategies.OPERATOR)
     val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error2<KtExpression, FirNamedFunctionSymbol, String>(SourceElementPositioningStrategies.OPERATOR)
     val PROPERTY_AS_OPERATOR by error1<PsiElement, FirPropertySymbol>(SourceElementPositioningStrategies.OPERATOR)
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityCompatibilityChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityCompatibilityChecker.kt
index 4f43062..e0262f2 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityCompatibilityChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirEqualityCompatibilityChecker.kt
@@ -5,98 +5,270 @@
 
 package org.jetbrains.kotlin.fir.analysis.checkers.expression
 
-import org.jetbrains.kotlin.KtRealSourceElementKind
 import org.jetbrains.kotlin.KtNodeTypes
-import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker
-import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker.isCompatible
+import org.jetbrains.kotlin.KtRealSourceElementKind
+import org.jetbrains.kotlin.KtSourceElement
 import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
 import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
 import org.jetbrains.kotlin.diagnostics.reportOn
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.analysis.checkers.isValueClass
+import org.jetbrains.kotlin.fir.declarations.utils.isClass
 import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
+import org.jetbrains.kotlin.fir.declarations.utils.isFinal
+import org.jetbrains.kotlin.fir.declarations.utils.isInterface
 import org.jetbrains.kotlin.fir.expressions.FirEqualityOperatorCall
+import org.jetbrains.kotlin.fir.expressions.FirExpression
 import org.jetbrains.kotlin.fir.expressions.FirOperation
-import org.jetbrains.kotlin.fir.render
+import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
+import org.jetbrains.kotlin.fir.resolve.isSubclassOf
 import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
 import org.jetbrains.kotlin.fir.types.*
 
 object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker() {
     override fun check(expression: FirEqualityOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
         val arguments = expression.argumentList.arguments
-        if (arguments.size != 2) return
-        val lType = arguments[0].typeRef.coneType
-        val rType = arguments[1].typeRef.coneType
-        checkCompatibility(lType, rType, context, expression, reporter)
-        checkSensibleness(lType, rType, context, expression, reporter)
+        if (arguments.size != 2) error("Equality operator call with non-2 arguments")
+
+        val (lArgument, lType) = arguments[0].selfWithMostOriginalTypeIfSmartCast
+        val (rArgument, rType) = arguments[1].selfWithMostOriginalTypeIfSmartCast
+
+        val checkApplicability = when (expression.operation) {
+            FirOperation.EQ, FirOperation.NOT_EQ -> ::checkEqualityApplicability
+            FirOperation.IDENTITY, FirOperation.NOT_IDENTITY -> ::checkIdentityApplicability
+            else -> error("Invalid operator of FirEqualityOperatorCall")
+        }
+
+        checkApplicability(lType, rType, context).ifInapplicable {
+            return reporter.reportOn(
+                expression, it, expression.operation,
+                isWarning = false, lType, rType, context,
+            )
+        }
+
+        if (lArgument !is FirSmartCastExpression && rArgument !is FirSmartCastExpression) {
+            return
+        }
+
+        val lSmartCastType = lArgument.typeRef.coneType
+        val rSmartCastType = rArgument.typeRef.coneType
+
+        checkApplicability(lSmartCastType, rSmartCastType, context).ifInapplicable {
+            return reporter.reportOn(
+                expression, it, expression.operation,
+                isWarning = true, lType, rType, context,
+            )
+        }
     }
 
-    private fun checkCompatibility(
+    private val FirExpression.selfWithMostOriginalTypeIfSmartCast
+        get() = this to mostOriginalTypeIfSmartCast
+
+    private val FirExpression.mostOriginalTypeIfSmartCast: ConeKotlinType
+        get() = when (this) {
+            is FirSmartCastExpression -> originalExpression.mostOriginalTypeIfSmartCast
+            else -> typeRef.coneType
+        }
+
+    private fun checkEqualityApplicability(
         lType: ConeKotlinType,
         rType: ConeKotlinType,
         context: CheckerContext,
-        expression: FirEqualityOperatorCall,
-        reporter: DiagnosticReporter
-    ) {
-        // If one of the type is already `Nothing?`, we skip reporting further comparison. This is to allow comparing with `null`, which has
-        // type `Nothing?`
-        if (lType.isNullableNothing || rType.isNullableNothing) return
-        val inferenceContext = context.session.typeContext
+    ): Applicability {
+        val oneIsBuiltin = lType.isBuiltin(context) || rType.isBuiltin(context)
+        val oneIsNotNull = !lType.isNullable || !rType.isNullable
 
-        val compatibility = try {
-            inferenceContext.isCompatible(lType, rType)
-        } catch (e: Throwable) {
-            throw IllegalStateException(
-                "Exception while determining type compatibility: lType: $lType, rType: $rType, " +
-                        "equality ${expression.render()}, " +
-                        "file ${context.containingFile?.name}",
-                e
+        // The compiler should only check comparisons
+        // when builtins are involved.
+
+        return when {
+            !oneIsBuiltin || !oneIsNotNull || !shouldReportAsPerRules1(lType, rType, context) -> Applicability.APPLICABLE
+            lType.isNullableNothing || rType.isNullableNothing -> Applicability.INAPPLICABLE_AS_SENSELESS
+            lType.isEnumType(context) || rType.isEnumType(context) -> Applicability.INAPPLICABLE_AS_ENUMS
+            else -> Applicability.GENERALLY_INAPPLICABLE
+        }
+    }
+
+    private fun ConeKotlinType.isBuiltin(context: CheckerContext) = isPrimitiveOrNullablePrimitive || isString || isEnumType(context)
+
+    private fun checkIdentityApplicability(
+        lType: ConeKotlinType,
+        rType: ConeKotlinType,
+        context: CheckerContext,
+    ): Applicability {
+        // The compiler should only check comparisons
+        // when identity-less types are involved.
+
+        return if (lType.isIdentityLess(context) || rType.isIdentityLess(context)) {
+            Applicability.INAPPLICABLE_AS_IDENTITY_LESS
+        } else {
+            Applicability.APPLICABLE
+        }
+    }
+
+    private fun ConeKotlinType.isIdentityLess(context: CheckerContext) = isIdentityLess(context.session)
+
+    private fun ConeKotlinType.isIdentityLess(session: FirSession) = isPrimitive || !isNullable && isValueClass(session)
+
+    private fun shouldReportAsPerRules1(lType: ConeKotlinType, rType: ConeKotlinType, context: CheckerContext): Boolean {
+        val lClass = lType.representativeClassType(context)
+        val rClass = rType.representativeClassType(context)
+
+        return areUnrelatedClasses(lClass, rClass, context)
+                || areInterfaceAndUnrelatedFinalClassAccordingly(lClass, rClass, context)
+                || areInterfaceAndUnrelatedFinalClassAccordingly(rClass, lClass, context)
+    }
+
+    private fun areUnrelatedClasses(lClass: FirClassSymbol<*>, rClass: FirClassSymbol<*>, context: CheckerContext): Boolean {
+        fun FirClassSymbol<*>.isSubclassOf(other: FirClassSymbol<*>) =
+            isSubclassOf(other.toLookupTag(), context.session, isStrict = false, lookupInterfaces = true)
+
+        return when {
+            !lClass.isClass || !rClass.isClass -> false
+            lClass.isFinal && rClass.isFinal && lClass != rClass -> true
+            else -> !lClass.isSubclassOf(rClass) && !rClass.isSubclassOf(lClass)
+        }
+    }
+
+    private fun areInterfaceAndUnrelatedFinalClassAccordingly(
+        lClass: FirClassSymbol<*>,
+        rClass: FirClassSymbol<*>,
+        context: CheckerContext,
+    ): Boolean {
+        return lClass.isInterface && rClass.isFinalClass && !rClass.isSubclassOf(
+            lClass.toLookupTag(), context.session, isStrict = false, lookupInterfaces = true,
+        )
+    }
+
+    private val FirClassSymbol<*>.isFinalClass get() = isClass && isFinal
+
+    private val representativeClassCache = mutableMapOf<ConeKotlinType, FirClassSymbol<*>>()
+
+    private fun ConeKotlinType.representativeClassType(context: CheckerContext): FirClassSymbol<*> {
+        val symbol = toSymbol(context.session)
+
+        if (symbol is FirClassSymbol<*> && (symbol.isClass || symbol.isInterface)) {
+            return symbol
+        }
+
+        return representativeClassType(context.session, representativeClassCache)
+    }
+
+    private fun ConeKotlinType.representativeClassType(
+        session: FirSession,
+        cache: MutableMap<ConeKotlinType, FirClassSymbol<*>>,
+    ): FirClassSymbol<*> = cache.getOrPut(this) {
+        when (val symbol = toSymbol(session)) {
+            is FirClassSymbol<*> -> when {
+                symbol.isClass -> symbol
+                else -> symbol.resolvedSuperTypes.firstNonAnyRepresentativeClass(session, cache)
+            }
+            is FirTypeParameterSymbol -> symbol.resolvedBounds.map { it.type }.firstNonAnyRepresentativeClass(session, cache)
+            is FirTypeAliasSymbol -> symbol.resolvedExpandedTypeRef.type.representativeClassType(session, cache)
+            else -> session.anyClassSymbol
+        }
+    }
+
+    private fun List<ConeKotlinType>.firstNonAnyRepresentativeClass(
+        session: FirSession,
+        cache: MutableMap<ConeKotlinType, FirClassSymbol<*>>,
+    ): FirClassSymbol<*> {
+        return firstNotNullOfOrNull { type ->
+            type.representativeClassType(session, cache).takeIf {
+                it != session.anyClassSymbol
+            }
+        } ?: session.anyClassSymbol
+    }
+
+    private val FirSession.anyClassSymbol
+        get() = builtinTypes.anyType.coneType.toSymbol(this) as? FirClassSymbol<*>
+            ?: error("Any type symbol is not a class symbol")
+
+    private enum class Applicability {
+        APPLICABLE,
+        GENERALLY_INAPPLICABLE,
+        INAPPLICABLE_AS_SENSELESS,
+        INAPPLICABLE_AS_ENUMS,
+        INAPPLICABLE_AS_IDENTITY_LESS,
+    }
+
+    private inline fun Applicability.ifInapplicable(block: (Applicability) -> Unit) = when (this) {
+        Applicability.APPLICABLE -> {}
+        else -> block(this)
+    }
+
+    private fun getGeneralInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.EQUALITY_NOT_APPLICABLE_WARNING
+        else -> FirErrors.EQUALITY_NOT_APPLICABLE
+    }
+
+    private fun getEnumInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.INCOMPATIBLE_ENUM_COMPARISON
+        else -> FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR
+    }
+
+    private fun getIdentityLessInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.FORBIDDEN_IDENTITY_EQUALS
+        else -> FirErrors.FORBIDDEN_IDENTITY_EQUALS_WARNING
+    }
+
+    private fun getSourceLessInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.INCOMPATIBLE_TYPES_WARNING
+        else -> FirErrors.INCOMPATIBLE_TYPES
+    }
+
+    private fun getSenselessComparisonInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.SENSELESS_COMPARISON
+        else -> FirErrors.SENSELESS_COMPARISON_ERROR
+    }
+
+    private fun getSenselessWhenBranchInapplicabilityDiagnostic(isWarning: Boolean) = when {
+        isWarning -> FirErrors.SENSELESS_NULL_IN_WHEN
+        else -> FirErrors.SENSELESS_NULL_IN_WHEN_ERROR
+    }
+
+    private fun isWhenBranch(source: KtSourceElement?, rType: ConeKotlinType): Boolean =
+        source?.elementType != KtNodeTypes.BINARY_EXPRESSION && rType.isNullableNothing
+
+    private fun DiagnosticReporter.reportOn(
+        expression: FirEqualityOperatorCall,
+        applicability: Applicability,
+        operation: FirOperation,
+        isWarning: Boolean,
+        lType: ConeKotlinType,
+        rType: ConeKotlinType,
+        context: CheckerContext,
+    ): Unit = when {
+        applicability == Applicability.INAPPLICABLE_AS_IDENTITY_LESS -> reportOn(
+            expression.source, getIdentityLessInapplicabilityDiagnostic(isWarning),
+            lType, rType, context,
+        )
+        expression.source?.kind !is KtRealSourceElementKind -> reportOn(
+            expression.source, getSourceLessInapplicabilityDiagnostic(isWarning),
+            lType, rType, context,
+        )
+        applicability == Applicability.GENERALLY_INAPPLICABLE -> reportOn(
+            expression.source, getGeneralInapplicabilityDiagnostic(isWarning),
+            operation.operator, lType, rType, context,
+        )
+        applicability == Applicability.INAPPLICABLE_AS_ENUMS -> reportOn(
+            expression.source, getEnumInapplicabilityDiagnostic(isWarning),
+            lType, rType, context,
+        )
+        applicability == Applicability.INAPPLICABLE_AS_SENSELESS -> when {
+            isWhenBranch(expression.source, rType) -> reportOn(
+                expression.source, getSenselessWhenBranchInapplicabilityDiagnostic(isWarning), context,
+            )
+            else -> reportOn(
+                expression.source, getSenselessComparisonInapplicabilityDiagnostic(isWarning),
+                expression, false, context,
             )
         }
-        if (compatibility != ConeTypeCompatibilityChecker.Compatibility.COMPATIBLE) {
-            when (expression.source?.kind) {
-                KtRealSourceElementKind -> {
-                    // Note: FE1.0 reports INCOMPATIBLE_ENUM_COMPARISON_ERROR only when TypeIntersector.isIntersectionEmpty() thinks the
-                    // given types are compatible. Exactly mimicking the behavior of FE1.0 is difficult and does not seem to provide any
-                    // value. So instead, we deterministically output INCOMPATIBLE_ENUM_COMPARISON_ERROR if at least one of the value is an
-                    // enum.
-                    if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE &&
-                        (lType.isEnumType(context) || rType.isEnumType(context))
-                    ) {
-                        reporter.reportOn(
-                            expression.source,
-                            FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR,
-                            lType,
-                            rType,
-                            context
-                        )
-                    } else {
-                        reporter.reportOn(
-                            expression.source,
-                            if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE) {
-                                FirErrors.EQUALITY_NOT_APPLICABLE
-                            } else {
-                                FirErrors.EQUALITY_NOT_APPLICABLE_WARNING
-                            },
-                            expression.operation.operator,
-                            lType,
-                            rType,
-                            context
-                        )
-                    }
-                }
-                else -> reporter.reportOn(
-                    expression.source,
-                    if (compatibility == ConeTypeCompatibilityChecker.Compatibility.HARD_INCOMPATIBLE) {
-                        FirErrors.INCOMPATIBLE_TYPES
-                    } else {
-                        FirErrors.INCOMPATIBLE_TYPES_WARNING
-                    },
-                    lType,
-                    rType,
-                    context
-                )
-            }
-        }
+        else -> error("Shouldn't be here")
     }
 
     private fun ConeKotlinType.isEnumType(
@@ -106,36 +278,4 @@
         val firRegularClassSymbol = (this as? ConeClassLikeType)?.lookupTag?.toFirRegularClassSymbol(context.session) ?: return false
         return firRegularClassSymbol.isEnumClass
     }
-
-    private fun checkSensibleness(
-        lType: ConeKotlinType,
-        rType: ConeKotlinType,
-        context: CheckerContext,
-        expression: FirEqualityOperatorCall,
-        reporter: DiagnosticReporter
-    ) {
-        val type = when {
-            rType.isNullableNothing -> lType
-            lType.isNullableNothing -> rType
-            else -> return
-        }
-        if (type is ConeErrorType) return
-        val isPositiveCompare = expression.operation == FirOperation.EQ || expression.operation == FirOperation.IDENTITY
-        val compareResult = with(context.session.typeContext) {
-            when {
-                // `null` literal has type `Nothing?`
-                type.isNullableNothing -> isPositiveCompare
-                !type.isNullableType() -> !isPositiveCompare
-                else -> return
-            }
-        }
-        // We only report `SENSELESS_NULL_IN_WHEN` if `lType = type` because `lType` is the type of the when subject. This diagnostic is
-        // only intended for cases where the branch condition contains a null. Also, the error message for SENSELESS_NULL_IN_WHEN
-        // says the value is *never* equal to null, so we can't report it if the value is *always* equal to null.
-        if (expression.source?.elementType != KtNodeTypes.BINARY_EXPRESSION && type === lType && !compareResult) {
-            reporter.reportOn(expression.source, FirErrors.SENSELESS_NULL_IN_WHEN, context)
-        } else {
-            reporter.reportOn(expression.source, FirErrors.SENSELESS_COMPARISON, expression, compareResult, context)
-        }
-    }
 }
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
index f44b09e..93445e9 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
@@ -216,6 +216,8 @@
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FLOAT_LITERAL_OUT_OF_RANGE
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_BINARY_MOD
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_BINARY_MOD
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_IDENTITY_EQUALS
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_IDENTITY_EQUALS_WARNING
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_VARARG_PARAMETER_TYPE
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_CALL_EXPECTED
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_DECLARATION_WITH_NO_NAME
@@ -255,6 +257,7 @@
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_TARGET_PROPERTY_HAS_NO_BACKING_FIELD
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_TARGET_PROPERTY_HAS_NO_DELEGATE
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_TARGET_PROPERTY_IMMUTABLE
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_ENUM_COMPARISON
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_ENUM_COMPARISON_ERROR
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_MODIFIERS
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_TYPES
@@ -485,7 +488,9 @@
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SEALED_SUPERTYPE_IN_LOCAL_CLASS
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SECONDARY_CONSTRUCTOR_WITH_BODY_INSIDE_VALUE_CLASS
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SENSELESS_COMPARISON
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SENSELESS_COMPARISON_ERROR
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SENSELESS_NULL_IN_WHEN
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SENSELESS_NULL_IN_WHEN_ERROR
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SETTER_PROJECTED_OUT
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SETTER_VISIBILITY_INCONSISTENT_WITH_PROPERTY_VISIBILITY
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SINGLETON_IN_SUPERTYPE
@@ -1804,7 +1809,9 @@
         map.put(FirErrors.WRONG_IMPLIES_CONDITION, "Wrong implies condition")
         map.put(UNREACHABLE_CODE, "Unreachable code", NOT_RENDERED, NOT_RENDERED)
         map.put(SENSELESS_COMPARISON, "Condition ''{0}'' is always ''{1}''", FIR, TO_STRING)
+        map.put(SENSELESS_COMPARISON_ERROR, "Condition ''{0}'' is always ''{1}''", FIR, TO_STRING)
         map.put(SENSELESS_NULL_IN_WHEN, "Expression under 'when' is never equal to null")
+        map.put(SENSELESS_NULL_IN_WHEN_ERROR, "Expression under 'when' is never equal to null")
 
         // Nullability
         map.put(USELESS_CALL_ON_NOT_NULL, "Unsafe call on not null")
@@ -1972,6 +1979,24 @@
             RENDER_TYPE,
             RENDER_TYPE
         )
+        map.put(
+            INCOMPATIBLE_ENUM_COMPARISON,
+            "Comparison of incompatible enums ''{0}'' and ''{1}'' is always unsuccessful",
+            RENDER_TYPE,
+            RENDER_TYPE
+        )
+        map.put(
+            FORBIDDEN_IDENTITY_EQUALS,
+            "Identity equality for arguments of types {0} and {1} is forbidden",
+            RENDER_TYPE,
+            RENDER_TYPE
+        )
+        map.put(
+            FORBIDDEN_IDENTITY_EQUALS_WARNING,
+            "Identity equality for arguments of types {0} and {1} is forbidden",
+            RENDER_TYPE,
+            RENDER_TYPE
+        )
         map.put(INC_DEC_SHOULD_NOT_RETURN_UNIT, "Functions inc(), dec() shouldn't return Unit to be used by operators ++, --")
         map.put(
             ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT,
diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirSymbolStatusUtils.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirSymbolStatusUtils.kt
index af47a2a..1546325 100644
--- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirSymbolStatusUtils.kt
+++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirSymbolStatusUtils.kt
@@ -83,6 +83,9 @@
     get() = classId.isLocal || this is FirAnonymousObjectSymbol
 
 
+inline val FirClassSymbol<*>.isClass: Boolean
+    get() = classKind.isClass
+
 inline val FirClassSymbol<*>.isInterface: Boolean
     get() = classKind.isInterface