[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