[FIR] Introduce new kind of integer literal types for constant operator calls

This commit is contained in:
Dmitriy Novozhilov
2022-02-04 17:14:33 +03:00
committed by teamcity
parent bb5217be72
commit b980f5ab32
12 changed files with 185 additions and 111 deletions
@@ -9,35 +9,28 @@ import org.jetbrains.kotlin.fir.isLong
import org.jetbrains.kotlin.fir.isULong
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralTypeExtensions.approximateIntegerLiteralBounds
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralTypeExtensions.createClassLikeType
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralTypeExtensions.createSupertypeList
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralTypeExtensions.getApproximatedTypeImpl
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralTypeExtensions.withNullability
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.SimpleTypeMarker
class ConeIntegerLiteralTypeImpl(
class ConeIntegerLiteralConstantTypeImpl(
value: Long,
override val possibleTypes: Collection<ConeClassLikeType>,
isUnsigned: Boolean,
nullability: ConeNullability
) : ConeIntegerLiteralType(value, isUnsigned, nullability) {
override val attributes: ConeAttributes
get() = ConeAttributes.Empty
) : ConeIntegerLiteralConstantType(value, isUnsigned, nullability) {
override val supertypes: List<ConeClassLikeType> by lazy {
listOf(
createType(StandardClassIds.Number),
ConeClassLikeTypeImpl(COMPARABLE_TAG, arrayOf(ConeKotlinTypeProjectionIn(this)), false)
)
createSupertypeList(this)
}
override fun getApproximatedType(expectedType: ConeKotlinType?): ConeClassLikeType {
val expectedTypeForApproximation = (expectedType?.lowerBoundIfFlexible() as? ConeClassLikeType)
?.withNullability(ConeNullability.NOT_NULL)
val approximatedType = when (expectedTypeForApproximation) {
null, !in possibleTypes -> possibleTypes.first()
else -> expectedTypeForApproximation
}
return approximatedType.withNullability(nullability)
return getApproximatedTypeImpl(expectedType)
}
@OptIn(ExperimentalUnsignedTypes::class)
@@ -81,7 +74,7 @@ class ConeIntegerLiteralTypeImpl(
}
}
} else {
ConeIntegerLiteralTypeImpl(value, possibleTypes, isUnsigned, nullability)
ConeIntegerLiteralConstantTypeImpl(value, possibleTypes, isUnsigned, nullability)
}
}
@@ -96,61 +89,42 @@ class ConeIntegerLiteralTypeImpl(
private val UBYTE_RANGE = UByte.MIN_VALUE.toLong()..UByte.MAX_VALUE.toLong()
private val USHORT_RANGE = UShort.MIN_VALUE.toLong()..UShort.MAX_VALUE.toLong()
private val UINT_RANGE = UInt.MIN_VALUE.toLong()..UInt.MAX_VALUE.toLong()
private val COMPARABLE_TAG = ConeClassLikeLookupTagImpl(StandardClassIds.Comparable)
fun findCommonSuperType(types: Collection<SimpleTypeMarker>): SimpleTypeMarker? {
return findCommonSuperTypeOrIntersectionType(types, Mode.COMMON_SUPER_TYPE)
}
fun findIntersectionType(types: Collection<SimpleTypeMarker>): SimpleTypeMarker? {
return findCommonSuperTypeOrIntersectionType(types, Mode.INTERSECTION_TYPE)
}
private enum class Mode {
COMMON_SUPER_TYPE, INTERSECTION_TYPE
}
/**
* intersection(ILT(types), PrimitiveType) = commonSuperType(ILT(types), PrimitiveType) =
* PrimitiveType in types -> PrimitiveType
* PrimitiveType !in types -> null
*
* intersection(ILT(types_1), ILT(types_2)) = ILT(types_1 union types_2)
*
* commonSuperType(ILT(types_1), ILT(types_2)) = ILT(types_1 intersect types_2)
*/
private fun findCommonSuperTypeOrIntersectionType(types: Collection<SimpleTypeMarker>, mode: Mode): SimpleTypeMarker? {
if (types.isEmpty()) return null
@Suppress("UNCHECKED_CAST")
return types.reduce { left: SimpleTypeMarker?, right: SimpleTypeMarker? -> fold(left, right, mode) }
}
private fun fold(left: SimpleTypeMarker?, right: SimpleTypeMarker?, mode: Mode): SimpleTypeMarker? {
if (left == null || right == null) return null
return when {
left is ConeIntegerLiteralType && right is ConeIntegerLiteralType ->
fold(left, right, mode)
left is ConeIntegerLiteralType -> fold(left, right)
right is ConeIntegerLiteralType -> fold(right, left)
else -> null
}
}
private fun fold(left: ConeIntegerLiteralType, right: ConeIntegerLiteralType, mode: Mode): ConeIntegerLiteralType {
val possibleTypes = when (mode) {
Mode.COMMON_SUPER_TYPE -> left.possibleTypes intersect right.possibleTypes
Mode.INTERSECTION_TYPE -> left.possibleTypes union right.possibleTypes
}
return ConeIntegerLiteralTypeImpl(left.value, possibleTypes, left.isUnsigned, left.nullability)
}
private fun fold(left: ConeIntegerLiteralType, right: SimpleTypeMarker): SimpleTypeMarker? =
if (right in left.possibleTypes) right else null
}
}
class ConeIntegerConstantOperatorTypeImpl(
isUnsigned: Boolean,
nullability: ConeNullability
) : ConeIntegerConstantOperatorType(isUnsigned, nullability) {
override val possibleTypes: Collection<ConeClassLikeType> = when (isUnsigned) {
false -> setOf(
createClassLikeType(StandardClassIds.Int),
createClassLikeType(StandardClassIds.Long),
)
true -> setOf(
createClassLikeType(StandardClassIds.UInt),
createClassLikeType(StandardClassIds.ULong),
)
}
override val supertypes: List<ConeClassLikeType> by lazy {
createSupertypeList(this)
}
override fun getApproximatedType(expectedType: ConeKotlinType?): ConeClassLikeType {
return getApproximatedTypeImpl(expectedType)
}
}
/**
* This methods detects common super type only for special rules for integer literal types
* If it returns null then CST will be found by regular rules using real supertypes
* of integer literal types
*/
fun ConeIntegerLiteralType.Companion.findCommonSuperType(types: Collection<SimpleTypeMarker>): SimpleTypeMarker? {
return ConeIntegerLiteralTypeExtensions.findCommonSuperType(types)
}
fun ConeKotlinType.approximateIntegerLiteralType(expectedType: ConeKotlinType? = null): ConeKotlinType {
return when (this) {
is ConeIntegerLiteralType -> getApproximatedType(expectedType)
@@ -159,26 +133,98 @@ fun ConeKotlinType.approximateIntegerLiteralType(expectedType: ConeKotlinType? =
}
}
private fun ConeFlexibleType.approximateIntegerLiteralBounds(expectedType: ConeKotlinType? = null): ConeFlexibleType {
val newLowerBound = lowerBound.approximateIntegerLiteralType(expectedType)
val newUpperBound = upperBound.approximateIntegerLiteralType(expectedType)
private object ConeIntegerLiteralTypeExtensions {
private val COMPARABLE_TAG = ConeClassLikeLookupTagImpl(StandardClassIds.Comparable)
if (newLowerBound !== lowerBound || newUpperBound !== upperBound) {
return ConeFlexibleType(
newLowerBound.lowerBoundIfFlexible(),
newUpperBound.upperBoundIfFlexible()
fun createSupertypeList(type: ConeIntegerLiteralType): List<ConeClassLikeType> {
return listOf(
createClassLikeType(StandardClassIds.Number),
ConeClassLikeTypeImpl(COMPARABLE_TAG, arrayOf(ConeKotlinTypeProjectionIn(type)), false)
)
}
return this
}
fun createClassLikeType(classId: ClassId): ConeClassLikeType {
return ConeClassLikeTypeImpl(ConeClassLikeLookupTagImpl(classId), emptyArray(), false)
}
private fun ConeClassLikeType.withNullability(nullability: ConeNullability): ConeClassLikeType {
if (nullability == this.nullability) return this
fun ConeIntegerLiteralType.getApproximatedTypeImpl(expectedType: ConeKotlinType?): ConeClassLikeType {
val expectedTypeForApproximation = (expectedType?.lowerBoundIfFlexible() as? ConeClassLikeType)
?.withNullability(ConeNullability.NOT_NULL)
val approximatedType = when (expectedTypeForApproximation) {
null, !in possibleTypes -> possibleTypes.first()
else -> expectedTypeForApproximation
}
return approximatedType.withNullability(nullability)
}
return when (this) {
is ConeErrorType -> this
is ConeClassLikeTypeImpl -> ConeClassLikeTypeImpl(lookupTag, typeArguments, nullability.isNullable)
else -> error("sealed")
fun findCommonSuperType(types: Collection<SimpleTypeMarker>): SimpleTypeMarker? {
if (types.isEmpty()) return null
@Suppress("UNCHECKED_CAST")
return types.reduce { left: SimpleTypeMarker?, right: SimpleTypeMarker? -> commonSuperType(left, right) }
}
private fun commonSuperType(left: SimpleTypeMarker?, right: SimpleTypeMarker?): SimpleTypeMarker? {
if (left == null || right == null) return null
return when {
left is ConeIntegerLiteralType && right !is ConeIntegerLiteralType -> {
commonSuperTypeBetweenIntegerTypeAndRegularType(left, right)
}
right is ConeIntegerLiteralType && left !is ConeIntegerLiteralType -> {
commonSuperTypeBetweenIntegerTypeAndRegularType(right, left)
}
left is ConeIntegerLiteralConstantType && right is ConeIntegerLiteralConstantType -> {
commonSuperTypeBetweenTwoConstantTypes(left, right)
}
left is ConeIntegerConstantOperatorType -> left
right is ConeIntegerConstantOperatorType -> right
else -> null
}
}
private fun commonSuperTypeBetweenIntegerTypeAndRegularType(
integerLiteralType: ConeIntegerLiteralType,
regularType: SimpleTypeMarker
): SimpleTypeMarker? {
return when (regularType) {
in integerLiteralType.possibleTypes -> regularType
else -> null
}
}
private fun commonSuperTypeBetweenTwoConstantTypes(
left: ConeIntegerLiteralConstantType,
right: ConeIntegerLiteralConstantType
): ConeIntegerLiteralConstantType {
val possibleTypes = left.possibleTypes intersect right.possibleTypes
return ConeIntegerLiteralConstantTypeImpl(left.value, possibleTypes, left.isUnsigned, left.nullability)
}
fun ConeFlexibleType.approximateIntegerLiteralBounds(expectedType: ConeKotlinType? = null): ConeFlexibleType {
val newLowerBound = lowerBound.approximateIntegerLiteralType(expectedType)
val newUpperBound = upperBound.approximateIntegerLiteralType(expectedType)
if (newLowerBound !== lowerBound || newUpperBound !== upperBound) {
return ConeFlexibleType(
newLowerBound.lowerBoundIfFlexible(),
newUpperBound.upperBoundIfFlexible()
)
}
return this
}
fun ConeClassLikeType.withNullability(nullability: ConeNullability): ConeClassLikeType {
if (nullability == this.nullability) return this
return when (this) {
is ConeErrorType -> this
is ConeClassLikeTypeImpl -> ConeClassLikeTypeImpl(lookupTag, typeArguments, nullability.isNullable)
else -> error("sealed")
}
}
}
@@ -8,14 +8,15 @@ package org.jetbrains.kotlin.fir.types
object ConeKotlinTypeComparator : Comparator<ConeKotlinType> {
private val ConeKotlinType.priority : Int
get() = when (this) {
is ConeErrorType -> 8
is ConeLookupTagBasedType -> 7
is ConeFlexibleType -> 6
is ConeCapturedType -> 5
is ConeDefinitelyNotNullType -> 4
is ConeIntersectionType -> 3
is ConeStubType -> 2
is ConeIntegerLiteralType -> 1
is ConeErrorType -> 9
is ConeLookupTagBasedType -> 8
is ConeFlexibleType -> 7
is ConeCapturedType -> 6
is ConeDefinitelyNotNullType -> 5
is ConeIntersectionType -> 4
is ConeStubType -> 3
is ConeIntegerLiteralConstantType -> 2
is ConeIntegerConstantOperatorType -> 1
else -> 0
}
@@ -151,8 +152,8 @@ object ConeKotlinTypeComparator : Comparator<ConeKotlinType> {
}
return compare(a.nullability, b.nullability)
}
is ConeIntegerLiteralType -> {
require(b is ConeIntegerLiteralType) {
is ConeIntegerLiteralConstantType -> {
require(b is ConeIntegerLiteralConstantType) {
"priority is inconsistent: ${a.render()} v.s. ${b.render()}"
}
val valueDiff = a.value - b.value
@@ -166,6 +167,9 @@ object ConeKotlinTypeComparator : Comparator<ConeKotlinType> {
// Can't compare individual types from each side, since their orders are not guaranteed.
return a.hashCode() - b.hashCode()
}
is ConeIntegerConstantOperatorType -> {
return compare(a.nullability, b.nullability)
}
else ->
error("Unsupported type comparison: ${a.render()} v.s. ${b.render()}")
}