[FIR] Relax incompatibleEnumAndUnrelatedInterface

This commit is contained in:
Nikolay Lunyak
2023-10-20 17:10:35 +03:00
committed by Space Team
parent 8ec7d128f5
commit ed8935899c
2 changed files with 40 additions and 15 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.isPrimitiveType import org.jetbrains.kotlin.fir.isPrimitiveType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.name.StandardClassIds
@@ -49,12 +50,15 @@ object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker() {
} }
checkApplicability(l.originalTypeInfo, r.originalTypeInfo, context).ifInapplicable { checkApplicability(l.originalTypeInfo, r.originalTypeInfo, context).ifInapplicable {
// Ideally this should match cases when K1 // K1 checks consist of 2 parts: reporting a
// sees a non-empty intersection and none of the // diagnostic if the intersection is empty,
// types is an enum, but intersections in K1 // and otherwise reporting a diagnostic if
// work differently from intersections in K2. // `isIncompatibleEnums` returns true.
val isCaseMissedByK1 = areBothTypeParameters(l.originalTypeInfo, r.originalTypeInfo) // In either case K1 may not report a diagnostic
|| areTypeParameterAndEnumClass(l.originalTypeInfo, r.originalTypeInfo) // due to some reasons, and we need to
// account for them.
val isCaseMissedByK1 = isCaseMissedByK1Intersector(l.originalType, r.originalType, context.session)
&& isCaseMissedByAdditionalK1IncompatibleEnumsCheck(l.originalType, r.originalType, context.session)
val replicateK1Behavior = !context.languageVersionSettings.supportsFeature(LanguageFeature.ReportErrorsForComparisonOperators) val replicateK1Behavior = !context.languageVersionSettings.supportsFeature(LanguageFeature.ReportErrorsForComparisonOperators)
return reporter.reportInapplicabilityDiagnostic( return reporter.reportInapplicabilityDiagnostic(
@@ -285,16 +289,28 @@ private class TypeInfo(
val isPrimitive: Boolean, val isPrimitive: Boolean,
val isBuiltin: Boolean, val isBuiltin: Boolean,
val isValueClass: Boolean, val isValueClass: Boolean,
val isLiterallyTypeParameter: Boolean,
) { ) {
override fun toString() = "$type" override fun toString() = "$type"
} }
private val FirClassSymbol<*>.isBuiltin get() = isPrimitiveType() || classId == StandardClassIds.String || isEnumClass private val FirClassSymbol<*>.isBuiltin get() = isPrimitiveType() || classId == StandardClassIds.String || isEnumClass
// This property is used to replicate K1 behavior, and it
// tries to match the `TypeUtils.canHaveSubtypes(typeChecker, type)`
// check in the K1 intersector.
// In K2 enum classes are final, though enum entries are their subclasses.
private fun ConeKotlinType.canHaveSubtypesAccordingToK1(session: FirSession): Boolean {
val symbol = toSymbol(session)
return when {
symbol is FirRegularClassSymbol && symbol.isEnumClass -> true
symbol is FirClassSymbol<*> && symbol.isFinalClass -> false
else -> true
}
}
// This property is used to replicate K1 behavior, and it // This property is used to replicate K1 behavior, and it
// tries to predict empty intersections from the K1 point-of-view. // tries to predict empty intersections from the K1 point-of-view.
// Enum classes are final, but enum entries are their subclasses.
private val TypeInfo.enforcesEmptyIntersection get() = isFinalClass && !isEnumClass private val TypeInfo.enforcesEmptyIntersection get() = isFinalClass && !isEnumClass
private val TypeInfo.isNullableEnum get() = isEnumClass && type.isNullable private val TypeInfo.isNullableEnum get() = isEnumClass && type.isNullable
@@ -308,6 +324,10 @@ private val FirClassSymbol<*>.isFinalClass get() = isClass && isFinal
// NB: This is what RULES1 means then it says "class". // NB: This is what RULES1 means then it says "class".
private val FirClassSymbol<*>.isClass get() = !isInterface private val FirClassSymbol<*>.isClass get() = !isInterface
private fun ConeKotlinType.isEnum(session: FirSession) = toRegularClassSymbol(session)?.isEnumClass == true
private fun ConeKotlinType.isClass(session: FirSession) = toRegularClassSymbol(session) != null
private fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo { private fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo {
val bounds = collectUpperBounds().map { type -> toKotlinType(type).replaceArgumentsWithStarProjections() } val bounds = collectUpperBounds().map { type -> toKotlinType(type).replaceArgumentsWithStarProjections() }
val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) } val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) }
@@ -317,11 +337,10 @@ private fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo {
return TypeInfo( return TypeInfo(
type, notNullType, type, notNullType,
isFinalClass = bounds.any { it.toClassSymbol(session)?.isFinalClass == true }, isFinalClass = bounds.any { it.toClassSymbol(session)?.isFinalClass == true },
isEnumClass = bounds.any { it.toClassSymbol(session)?.isEnumClass == true }, isEnumClass = bounds.any { it.isEnum(session) },
isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive }, isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive },
isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true }, isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true },
isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true }, isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true },
isLiterallyTypeParameter = this.lowerBoundIfFlexible() is ConeTypeParameterType,
) )
} }
@@ -359,8 +378,14 @@ private fun FirExpression.toArgumentInfo(context: CheckerContext) =
this, resolvedType, mostOriginalTypeIfSmartCast.fullyExpandedType(context.session), context.session, this, resolvedType, mostOriginalTypeIfSmartCast.fullyExpandedType(context.session), context.session,
) )
private fun areBothTypeParameters(a: TypeInfo, b: TypeInfo) = private fun isCaseMissedByK1Intersector(a: ConeKotlinType, b: ConeKotlinType, session: FirSession) =
a.isLiterallyTypeParameter && b.isLiterallyTypeParameter a.canHaveSubtypesAccordingToK1(session) && b.canHaveSubtypesAccordingToK1(session)
private fun areTypeParameterAndEnumClass(a: TypeInfo, b: TypeInfo) = private fun isCaseMissedByAdditionalK1IncompatibleEnumsCheck(a: ConeKotlinType, b: ConeKotlinType, session: FirSession): Boolean {
a.isLiterallyTypeParameter && b.isEnumClass || b.isLiterallyTypeParameter && a.isEnumClass return when {
!a.isEnum(session) && !b.isEnum(session) -> true
a.isNullable && b.isNullable -> true
a.isNothingOrNullableNothing || b.isNothingOrNullableNothing -> true
else -> !a.isClass(session) || !b.isClass(session)
}
}
@@ -111,5 +111,5 @@ fun <I : A> incompatibleEnumAndUnrelatedInterfaceThroughTypeParameter(x: G?, i:
} }
fun incompatibleEnumAndUnrelatedInterface(x: G?, i: A?) { fun incompatibleEnumAndUnrelatedInterface(x: G?, i: A?) {
<!INCOMPATIBLE_ENUM_COMPARISON_ERROR!>x == i<!> <!INCOMPATIBLE_ENUM_COMPARISON!>x == i<!>
} }