[FIR] Don't lose upperBoundForApproximation when approximating intersection types
#KT-68207 Fixed
diff --git a/compiler/fir/analysis-tests/test/org/jetbrains/kotlin/test/FirApproximationTest.kt b/compiler/fir/analysis-tests/test/org/jetbrains/kotlin/test/FirApproximationTest.kt
new file mode 100644
index 0000000..f41343e
--- /dev/null
+++ b/compiler/fir/analysis-tests/test/org/jetbrains/kotlin/test/FirApproximationTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.test
+
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
+import org.jetbrains.kotlin.fir.symbols.lazyDeclarationResolver
+import org.jetbrains.kotlin.fir.types.*
+import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
+import org.jetbrains.kotlin.name.StandardClassIds
+import org.jetbrains.kotlin.test.builders.testRunner
+import org.jetbrains.kotlin.test.frontend.fir.handlers.FirCompilerLazyDeclarationResolverWithPhaseChecking
+import org.jetbrains.kotlin.test.model.FrontendKinds
+import org.jetbrains.kotlin.test.runners.AbstractFirPsiDiagnosticTest
+import org.jetbrains.kotlin.test.services.dependencyProvider
+import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+
+class FirApproximationTest : AbstractFirPsiDiagnosticTest() {
+ private val emptyFilePath = "compiler/fir/analysis-tests/testData/dummy/empty.kt"
+
+ @Test
+ fun `approximation of intersection type with upper bound`() {
+ runWithSession { session ->
+ val intersectionType = ConeIntersectionType(
+ listOf(
+ ConeIntegerLiteralConstantTypeImpl.create(1, false, { true }, ConeNullability.NOT_NULL),
+ ConeClassLikeTypeImpl(StandardClassIds.CharSequence.toLookupTag(), arrayOf(), false)
+ ),
+ ConeClassLikeTypeImpl(StandardClassIds.Number.toLookupTag(), emptyArray(), false)
+ )
+
+ val approximatedType = session.typeApproximator.approximateToSuperType(
+ intersectionType,
+ TypeApproximatorConfiguration.IntegerLiteralsTypesApproximation
+ ) as ConeIntersectionType
+
+ assertTrue(approximatedType.intersectedTypes.none { it is ConeIntegerLiteralConstantType })
+ assertNotNull(approximatedType.upperBoundForApproximation)
+ }
+ }
+
+ private fun runWithSession(f: (FirSession) -> Unit) {
+ testRunner(emptyFilePath, configuration).runTest(emptyFilePath) { configuration ->
+ val artifact = configuration.testServices.dependencyProvider
+ .let { it.getArtifactSafe(it.getTestModule("main"), FrontendKinds.FIR) }!!
+ val session = artifact.partsForDependsOnModules.first().session
+
+ (session.lazyDeclarationResolver as? FirCompilerLazyDeclarationResolverWithPhaseChecking)?.startResolvingPhase(
+ FirResolvePhase.BODY_RESOLVE
+ )
+
+ f(session)
+ }
+ }
+}
\ No newline at end of file
diff --git a/compiler/fir/analysis-tests/testData/dummy/empty.fir.txt b/compiler/fir/analysis-tests/testData/dummy/empty.fir.txt
new file mode 100644
index 0000000..0a581dc
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/dummy/empty.fir.txt
@@ -0,0 +1 @@
+FILE: empty.kt
diff --git a/compiler/fir/analysis-tests/testData/dummy/empty.kt b/compiler/fir/analysis-tests/testData/dummy/empty.kt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/dummy/empty.kt
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt
index e782338..d01c8de 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt
@@ -591,6 +591,10 @@
return intersectionType.withUpperBound(secondCandidate)
}
+ override fun KotlinTypeMarker.getUpperBoundForApproximationOfIntersectionType(): KotlinTypeMarker? {
+ return (this as? ConeIntersectionType)?.upperBoundForApproximation
+ }
+
override fun useRefinedBoundsForTypeVariableInFlexiblePosition(): Boolean = session.languageVersionSettings.supportsFeature(
LanguageFeature.JavaTypeParameterDefaultRepresentationWithDNN
)
diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt
index 02d8265..5bad2b7 100644
--- a/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt
+++ b/compiler/resolution.common/src/org/jetbrains/kotlin/types/AbstractTypeApproximator.kt
@@ -305,7 +305,12 @@
* For other case -- it's impossible to find some type except Nothing as subType for intersection type.
*/
val baseResult = when (conf.intersection) {
- TypeApproximatorConfiguration.IntersectionStrategy.ALLOWED -> if (!thereIsApproximation) return null else intersectTypes(newTypes)
+ TypeApproximatorConfiguration.IntersectionStrategy.ALLOWED -> if (!thereIsApproximation) {
+ return null
+ } else {
+ val upperBoundForApproximation = type.getUpperBoundForApproximationOfIntersectionType()
+ intersectTypes(newTypes, upperBoundForApproximation, toSuper, conf, depth)
+ }
TypeApproximatorConfiguration.IntersectionStrategy.TO_FIRST -> if (toSuper) newTypes.first() else return type.defaultResult(toSuper = false)
// commonSupertypeCalculator should handle flexible types correctly
TypeApproximatorConfiguration.IntersectionStrategy.TO_COMMON_SUPERTYPE,
@@ -319,11 +324,33 @@
return if (type.isMarkedNullable()) baseResult.withNullability(true) else baseResult
}
+ private fun intersectTypes(
+ newTypes: List<KotlinTypeMarker>,
+ upperBoundForApproximation: KotlinTypeMarker?,
+ toSuper: Boolean,
+ conf: TypeApproximatorConfiguration,
+ depth: Int,
+ ): KotlinTypeMarker {
+ val intersectionType = intersectTypes(newTypes)
+
+ if (upperBoundForApproximation == null) {
+ return intersectionType
+ }
+
+ val alternativeTypeApproximated = if (toSuper) {
+ approximateToSuperType(upperBoundForApproximation, conf, depth)
+ } else {
+ approximateToSubType(upperBoundForApproximation, conf, depth)
+ } ?: upperBoundForApproximation
+
+ return createTypeWithUpperBoundForIntersectionResult(intersectionType, alternativeTypeApproximated)
+ }
+
private fun approximateCapturedType(
capturedType: CapturedTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
- depth: Int
+ depth: Int,
): KotlinTypeMarker? {
fun KotlinTypeMarker.replaceRecursionWithStarProjection(capturedType: CapturedTypeMarker): KotlinTypeMarker {
// This replacement is important for resolving the code like below in K2.
diff --git a/core/compiler.common/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt b/core/compiler.common/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt
index e0e8ca4..13cde97 100644
--- a/core/compiler.common/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt
+++ b/core/compiler.common/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt
@@ -241,6 +241,11 @@
secondCandidate: KotlinTypeMarker
): KotlinTypeMarker
+ /**
+ * Only for K2
+ */
+ fun KotlinTypeMarker.getUpperBoundForApproximationOfIntersectionType() : KotlinTypeMarker? = null
+
fun KotlinTypeMarker.isSpecial(): Boolean
fun TypeConstructorMarker.isTypeVariable(): Boolean