From 45e884ec6714615455224609861c6c1d0a5997d4 Mon Sep 17 00:00:00 2001 From: pyos Date: Tue, 18 Oct 2022 13:34:53 +0200 Subject: [PATCH] FIR: refactor ConeTypeIntersector --- .../kotlin/fir/types/ConeTypeIntersector.kt | 130 +++++------------- 1 file changed, 38 insertions(+), 92 deletions(-) diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt index 20fc41b1f7e..b5a01ebe7cc 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt @@ -30,53 +30,35 @@ object ConeTypeIntersector { * UNKNOWN means, that we do not know, i.e. more precisely, all singleClassifier types marked nullable if any, * and other types is captured types or type parameters without not-null upper bound. Example: `String? & T` such types we should leave as is. */ - val resultNullability = inputTypes.fold(ResultNullability.START) { nullability, nextType -> - nullability.combine(nextType.type, context) + val isResultNullable = inputTypes.all { it.isNullable(context) } + val inputTypesWithNullability = inputTypes.mapTo(LinkedHashSet()) { + if (isResultNullable) it else it.makeConeTypeDefinitelyNotNullOrNotNull(context) } + if (inputTypesWithNullability.size == 1) return inputTypesWithNullability.single() - val inputTypesWithCorrectNullability = inputTypes.mapTo(LinkedHashSet()) { - if (resultNullability == ResultNullability.NOT_NULL) with(context) { - it.makeDefinitelyNotNullOrNotNull() as ConeKotlinType - } else it - } + /* + * Here we drop types from intersection set for cases like that: + * + * interface A + * interface B : A + * + * type = (A & B & ...) + * + * We want to drop A from that set, because it's useless for type checking. But in case if + * A came from inference and B came from smartcast we want to save both types in intersection + */ + val resultList = inputTypesWithNullability.toMutableList() + resultList.removeIfNonSingleErrorOrInRelation { candidate, other -> other.isStrictSubtypeOf(context, candidate) } + assert(resultList.isNotEmpty()) { "no types left after removing strict supertypes: ${inputTypes.joinToString()}" } - return intersectTypesWithoutIntersectionType(context, inputTypesWithCorrectNullability) - } - - private fun intersectTypesWithoutIntersectionType( - context: ConeTypeContext, - inputTypes: Set - ): ConeKotlinType { - if (inputTypes.size == 1) return inputTypes.single().type - - // Any and Nothing should leave - // Note that duplicates should be dropped because we have Set here. - val errorMessage = { "This collections cannot be empty! input types: ${inputTypes.joinToString()}" } - - val filteredEqualTypes = filterTypes(inputTypes) { lower, upper -> - /* - * Here we drop types from intersection set for cases like that: - * - * interface A - * interface B : A - * - * type = (A & B & ...) - * - * We want to drop A from that set, because it's useless for type checking. But in case if - * A came from inference and B came from smartcast we want to safe both types in intersection - */ - isStrictSupertype(context, lower, upper) - } - assert(filteredEqualTypes.isNotEmpty(), errorMessage) - - ConeIntegerLiteralIntersector.findCommonIntersectionType(filteredEqualTypes)?.let { return it } + ConeIntegerLiteralIntersector.findCommonIntersectionType(resultList)?.let { return it } /* * For the case like it(ft(String..String?), String?), where ft(String..String?) == String?, we prefer to _keep_ flexible type. * When a == b, the former, i.e., the one in the list will be filtered out, and the other one will remain. * So, here, we sort the interim list such that flexible types appear later. */ - val sortedEqualTypes = filteredEqualTypes.sortedWith { p0, p1 -> + resultList.sortWith { p0, p1 -> when { p0 is ConeFlexibleType && p1 is ConeFlexibleType -> 0 p0 is ConeFlexibleType -> 1 @@ -84,37 +66,23 @@ object ConeTypeIntersector { else -> 0 } } - val filteredSuperAndEqualTypes = filterTypes(sortedEqualTypes) { a, b -> - AbstractTypeChecker.equalTypes(context, a, b) - } - assert(filteredSuperAndEqualTypes.isNotEmpty(), errorMessage) - - if (filteredSuperAndEqualTypes.size < 2) return filteredSuperAndEqualTypes.single() - - return ConeIntersectionType(filteredSuperAndEqualTypes) + resultList.removeIfNonSingleErrorOrInRelation { candidate, other -> AbstractTypeChecker.equalTypes(context, candidate, other) } + assert(resultList.isNotEmpty()) { "no types left after removing equal types: ${inputTypes.joinToString()}" } + return resultList.singleOrNull() ?: ConeIntersectionType(resultList) } - private fun filterTypes( - inputTypes: Collection, - predicate: (lower: ConeKotlinType, upper: ConeKotlinType) -> Boolean - ): List { - val filteredTypes = ArrayList(inputTypes) - val iterator = filteredTypes.iterator() + private fun MutableCollection.removeIfNonSingleErrorOrInRelation( + predicate: (candidate: ConeKotlinType, other: ConeKotlinType) -> Boolean + ) { + val iterator = iterator() while (iterator.hasNext()) { - val upper = iterator.next() - if (filteredTypes.any { lower -> lower !== upper && predicate(lower, upper) } || - upper is ConeErrorType && filteredTypes.size > 1 + val candidate = iterator.next() + if (candidate is ConeErrorType && size > 1 || + any { other -> other !== candidate && predicate(candidate, other) } ) { iterator.remove() } } - return filteredTypes - } - - private fun isStrictSupertype(context: ConeTypeContext, subtype: ConeKotlinType, supertype: ConeKotlinType): Boolean { - return with(AbstractTypeChecker) { - isSubtypeOf(context, subtype, supertype) && !isSubtypeOf(context, supertype, subtype) - } } private fun flatIntersectionTypes( @@ -132,35 +100,13 @@ object ConeTypeIntersector { } } - private enum class ResultNullability { - START { - override fun combine(nextType: ConeKotlinType, context: ConeTypeContext): ResultNullability = - nextType.resultNullability(context) - }, - ACCEPT_NULL { - override fun combine(nextType: ConeKotlinType, context: ConeTypeContext): ResultNullability = - nextType.resultNullability(context) - }, + private fun ConeKotlinType.isStrictSubtypeOf(context: ConeTypeContext, supertype: ConeKotlinType): Boolean = + AbstractTypeChecker.isSubtypeOf(context, this, supertype) && !AbstractTypeChecker.isSubtypeOf(context, supertype, this) - // example: type parameter without not-null supertype - UNKNOWN { - override fun combine(nextType: ConeKotlinType, context: ConeTypeContext): ResultNullability = - nextType.resultNullability(context).let { - if (it == ACCEPT_NULL) this else it - } - }, - NOT_NULL { - override fun combine(nextType: ConeKotlinType, context: ConeTypeContext): ResultNullability = this - }; - - abstract fun combine(nextType: ConeKotlinType, context: ConeTypeContext): ResultNullability - - protected fun ConeKotlinType.resultNullability(context: ConeTypeContext): ResultNullability = - when { - isMarkedNullable -> ACCEPT_NULL - this is ConeFlexibleType -> upperBound.resultNullability(context) - ConeNullabilityChecker.isSubtypeOfAny(context, this) -> NOT_NULL - else -> UNKNOWN - } - } + private fun ConeKotlinType.isNullable(context: ConeTypeContext): Boolean = + when { + isMarkedNullable -> true + this is ConeFlexibleType -> upperBound.isNullable(context) + else -> !ConeNullabilityChecker.isSubtypeOfAny(context, this) + } }