[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