[FIR] Move some functions from the equality checker into a separate utils file

They will be used later for casts
This commit is contained in:
Nikolay Lunyak
2024-02-27 18:02:49 +02:00
committed by Space Team
parent cd3202ddd4
commit 5ce3812f39
2 changed files with 121 additions and 107 deletions
@@ -0,0 +1,115 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.expressions.FirWhenSubjectExpression
import org.jetbrains.kotlin.fir.isPrimitiveType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.text
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
internal fun FirExpression.unwrapToMoreUsefulExpression() = when (this) {
is FirWhenSubjectExpression -> whenRef.value.subject ?: this
else -> this
}
internal class TypeInfo(
val type: ConeKotlinType,
val notNullType: ConeKotlinType,
val isEnumClass: Boolean,
val isPrimitive: Boolean,
val isBuiltin: Boolean,
val isValueClass: Boolean,
val isFinal: Boolean,
val canHaveSubtypesAccordingToK1: Boolean,
) {
override fun toString() = "$type"
}
private val FirClassSymbol<*>.isBuiltin get() = isPrimitiveType() || classId == StandardClassIds.String || isEnumClass
internal val TypeInfo.isNullableEnum get() = isEnumClass && type.isNullable
internal val TypeInfo.isIdentityLess get() = isPrimitive || isValueClass
internal val TypeInfo.isNotNullPrimitive get() = isPrimitive && !type.isNullable
private val FirClassSymbol<*>.isFinalClass get() = isClass && isFinal
// NB: This is what RULES1 means then it says "class".
private val FirClassSymbol<*>.isClass get() = !isInterface
internal fun ConeKotlinType.isEnum(session: FirSession) = toRegularClassSymbol(session)?.isEnumClass == true
internal fun ConeKotlinType.isClass(session: FirSession) = toRegularClassSymbol(session) != null
internal fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo {
val bounds = collectUpperBounds().map { type -> type.toKotlinTypeIfPlatform(session).replaceArgumentsWithStarProjections() }
val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) }
?: session.builtinTypes.nullableAnyType.type
val notNullType = type.withNullability(ConeNullability.NOT_NULL, session.typeContext)
return TypeInfo(
type, notNullType,
isEnumClass = bounds.any { it.isEnum(session) },
isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive },
isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true },
isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true },
isFinal = bounds.any { it.toClassSymbol(session)?.isFinalClass == true },
// In K1's intersector, `canHaveSubtypes()` is called for `nullabilityStripped`.
withNullability(ConeNullability.NOT_NULL, session.typeContext).canHaveSubtypesAccordingToK1(session),
)
}
private fun ConeClassLikeType.toKotlinTypeIfPlatform(session: FirSession): ConeClassLikeType {
val kotlinClassId = session.platformClassMapper.getCorrespondingKotlinClass(lookupTag.classId)
return kotlinClassId?.constructClassLikeType(typeArguments, isNullable, attributes) ?: this
}
internal class ArgumentInfo(
val argument: FirExpression,
val userType: ConeKotlinType,
val originalType: ConeKotlinType,
val session: FirSession,
) {
val smartCastType: ConeKotlinType by lazy {
if (argument !is FirSmartCastExpression) originalType else userType.fullyExpandedType(session)
}
val originalTypeInfo get() = originalType.toTypeInfo(session)
val smartCastTypeInfo get() = smartCastType.toTypeInfo(session)
override fun toString() = "${argument.source?.text} :: $userType"
}
@Suppress("RecursivePropertyAccessor")
private val FirExpression.mostOriginalTypeIfSmartCast: ConeKotlinType
get() = when (this) {
is FirSmartCastExpression -> originalExpression.mostOriginalTypeIfSmartCast
else -> resolvedType
}
internal fun FirExpression.toArgumentInfo(context: CheckerContext) =
ArgumentInfo(
this,
userType = resolvedType.finalApproximationOrSelf(context),
originalType = mostOriginalTypeIfSmartCast.fullyExpandedType(context.session).finalApproximationOrSelf(context),
context.session,
)
@@ -12,24 +12,13 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.finalApproximationOrSelf
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.isPrimitiveType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.expressions.FirEqualityOperatorCall
import org.jetbrains.kotlin.fir.expressions.FirOperation
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.text
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker(MppCheckerKind.Common) {
override fun check(expression: FirEqualityOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
@@ -79,11 +68,6 @@ object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker(MppCheck
}
}
private fun FirExpression.unwrapToMoreUsefulExpression() = when (this) {
is FirWhenSubjectExpression -> whenRef.value.subject ?: this
else -> this
}
private fun checkEqualityApplicability(l: TypeInfo, r: TypeInfo, context: CheckerContext): Applicability {
val oneIsBuiltin = l.isBuiltin || r.isBuiltin
val oneIsIdentityLess = l.isIdentityLess || r.isIdentityLess
@@ -290,91 +274,6 @@ object FirEqualityCompatibilityChecker : FirEqualityOperatorCallChecker(MppCheck
}
}
private class TypeInfo(
val type: ConeKotlinType,
val notNullType: ConeKotlinType,
val isEnumClass: Boolean,
val isPrimitive: Boolean,
val isBuiltin: Boolean,
val isValueClass: Boolean,
val isFinal: Boolean,
val canHaveSubtypesAccordingToK1: Boolean,
) {
override fun toString() = "$type"
}
private val FirClassSymbol<*>.isBuiltin get() = isPrimitiveType() || classId == StandardClassIds.String || isEnumClass
private val TypeInfo.isNullableEnum get() = isEnumClass && type.isNullable
private val TypeInfo.isIdentityLess get() = isPrimitive || isValueClass
private val TypeInfo.isNotNullPrimitive get() = isPrimitive && !type.isNullable
private val FirClassSymbol<*>.isFinalClass get() = isClass && isFinal
// NB: This is what RULES1 means then it says "class".
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 {
val bounds = collectUpperBounds().map { type -> type.toKotlinTypeIfPlatform(session).replaceArgumentsWithStarProjections() }
val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) }
?: session.builtinTypes.nullableAnyType.type
val notNullType = type.withNullability(ConeNullability.NOT_NULL, session.typeContext)
return TypeInfo(
type, notNullType,
isEnumClass = bounds.any { it.isEnum(session) },
isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive },
isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true },
isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true },
isFinal = bounds.any { it.toClassSymbol(session)?.isFinalClass == true },
// In K1's intersector, `canHaveSubtypes()` is called for `nullabilityStripped`.
withNullability(ConeNullability.NOT_NULL, session.typeContext).canHaveSubtypesAccordingToK1(session),
)
}
private fun ConeClassLikeType.toKotlinTypeIfPlatform(session: FirSession): ConeClassLikeType {
val kotlinClassId = session.platformClassMapper.getCorrespondingKotlinClass(lookupTag.classId)
return kotlinClassId?.constructClassLikeType(typeArguments, isNullable, attributes) ?: this
}
private class ArgumentInfo(
val argument: FirExpression,
val userType: ConeKotlinType,
val originalType: ConeKotlinType,
val session: FirSession,
) {
val smartCastType: ConeKotlinType by lazy {
if (argument !is FirSmartCastExpression) originalType else userType.fullyExpandedType(session)
}
val originalTypeInfo get() = originalType.toTypeInfo(session)
val smartCastTypeInfo get() = smartCastType.toTypeInfo(session)
override fun toString() = "${argument.source?.text} :: $userType"
}
@Suppress("RecursivePropertyAccessor")
private val FirExpression.mostOriginalTypeIfSmartCast: ConeKotlinType
get() = when (this) {
is FirSmartCastExpression -> originalExpression.mostOriginalTypeIfSmartCast
else -> resolvedType
}
private fun FirExpression.toArgumentInfo(context: CheckerContext) =
ArgumentInfo(
this,
userType = resolvedType.finalApproximationOrSelf(context),
originalType = mostOriginalTypeIfSmartCast.fullyExpandedType(context.session).finalApproximationOrSelf(context),
context.session,
)
/**
* Unfortunately, intersections in K1 are not
* smart enough: K1 doesn't say that the
@@ -387,7 +286,7 @@ private fun FirExpression.toArgumentInfo(context: CheckerContext) =
*
* See: [org.jetbrains.kotlin.types.TypeIntersector.intersectTypes]
*/
private fun isCaseMissedByK1Intersector(a: TypeInfo, b: TypeInfo) =
internal fun isCaseMissedByK1Intersector(a: TypeInfo, b: TypeInfo) =
a.canHaveSubtypesAccordingToK1 && b.canHaveSubtypesAccordingToK1
/**
@@ -397,7 +296,7 @@ private fun isCaseMissedByK1Intersector(a: TypeInfo, b: TypeInfo) =
*
* See: [org.jetbrains.kotlin.types.isIncompatibleEnums]
*/
private fun isCaseMissedByAdditionalK1IncompatibleEnumsCheck(a: ConeKotlinType, b: ConeKotlinType, session: FirSession): Boolean {
internal fun isCaseMissedByAdditionalK1IncompatibleEnumsCheck(a: ConeKotlinType, b: ConeKotlinType, session: FirSession): Boolean {
return when {
!a.isEnum(session) && !b.isEnum(session) -> true
a.isNullable && b.isNullable -> true