[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