diff --git a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenNothingFields.kt b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenNothingFields.kt
new file mode 100644
index 0000000000000..bcdfeed4f295a
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenNothingFields.kt
@@ -0,0 +1,21 @@
+// FIR_IDENTICAL
+// RUN_PIPELINE_TILL: BACKEND
+// ISSUE: KT-77979
+// WITH_STDLIB
+
+sealed interface Adt
+data class A(val x: B) : Adt
+sealed interface BAdt : Adt
+data class B(val x: B) : BAdt
+data class C(val x: A) : BAdt
+
+fun example(a: Adt) {
+ when (a) {
+ is A -> {}
+ is C -> {}
+ // no need in case B
+ }
+}
+
+/* GENERATED_FIR_TAGS: classDeclaration, data, functionDeclaration, interfaceDeclaration, isExpression, nullableType,
+primaryConstructor, propertyDeclaration, sealed, smartcast, typeParameter, whenExpression, whenWithSubject */
diff --git a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenViolateUpperBounds.kt b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenViolateUpperBounds.kt
new file mode 100644
index 0000000000000..f974233fbd801
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/positive/exhaustiveWhenViolateUpperBounds.kt
@@ -0,0 +1,18 @@
+// FIR_IDENTICAL
+// RUN_PIPELINE_TILL: BACKEND
+// ISSUE: KT-77979
+// WITH_STDLIB
+
+sealed interface Adt
+data class A(val x: A) : Adt
+data class B(val x: Unit) : Adt
+
+fun example(a: Adt) {
+ when (a) {
+ is B -> {}
+ // no need in case A
+ }
+}
+
+/* GENERATED_FIR_TAGS: classDeclaration, data, functionDeclaration, interfaceDeclaration, isExpression, nullableType,
+out, primaryConstructor, propertyDeclaration, sealed, typeConstraint, typeParameter, whenExpression, whenWithSubject */
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt
index 5c2573ec98d5a..dafdc0f2df57c 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirUpperBoundViolatedHelpers.kt
@@ -186,6 +186,40 @@ fun checkUpperBoundViolated(
}
}
+fun checkUpperBoundViolatedNoReport(
+ typeParameters: List,
+ typeArguments: List,
+ session: FirSession,
+): Boolean {
+ val substitution = typeParameters.zip(typeArguments).toMap()
+ val substitutor = FE10LikeConeSubstitutor(substitution, session)
+
+ val count = minOf(typeParameters.size, typeArguments.size)
+ val typeSystemContext = session.typeContext
+
+ for (index in 0 until count) {
+ val argument = typeArguments[index]
+ val argumentType = argument.type
+ if (argumentType != null) {
+ if (argumentType.typeArguments.isEmpty() && argumentType !is ConeTypeParameterType) {
+ val intersection =
+ typeSystemContext.intersectTypes(typeParameters[index].resolvedBounds.map { it.coneType })
+ val upperBound = substitutor.substituteOrSelf(intersection)
+ if (!AbstractTypeChecker.isSubtypeOf(
+ typeSystemContext,
+ argumentType,
+ upperBound,
+ stubTypesEqualToAnything = true
+ )
+ ) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
/**
* @returns true if the diagnostic was reported
*/
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt
index cf32d9c0f3842..d77797bf71d88 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt
@@ -23,8 +23,8 @@ fun FirSession.doUnify(
targetTypeParameters: Set,
result: MutableMap,
): Boolean {
- val originalType = originalTypeProjection.type?.lowerBoundIfFlexible()?.fullyExpandedType(this)
- val typeWithParameters = typeWithParametersProjection.type?.lowerBoundIfFlexible()?.fullyExpandedType(this)
+ val originalType = originalTypeProjection.type?.lowerBoundIfFlexible()?.fullyExpandedType()
+ val typeWithParameters = typeWithParametersProjection.type?.lowerBoundIfFlexible()?.fullyExpandedType()
if (typeWithParameters is ConeErrorType) {
return true // Return true to avoid loosing `result` substitution
diff --git a/compiler/fir/resolve/build.gradle.kts b/compiler/fir/resolve/build.gradle.kts
index eff38e289fe3b..c4caaf7e516b7 100644
--- a/compiler/fir/resolve/build.gradle.kts
+++ b/compiler/fir/resolve/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
dependencies {
api(project(":compiler:fir:providers"))
api(project(":compiler:fir:semantics"))
+ api(project(":compiler:fir:checkers"))
implementation(project(":core:util.runtime"))
compileOnly(libs.guava)
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessComputer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessComputer.kt
index a249c225e7b2c..d62d0fc78d912 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessComputer.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessComputer.kt
@@ -11,8 +11,10 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.*
+import org.jetbrains.kotlin.fir.analysis.checkers.checkUpperBoundViolatedNoReport
import org.jetbrains.kotlin.fir.declarations.FirEnumEntry
import org.jetbrains.kotlin.fir.declarations.FirFile
+import org.jetbrains.kotlin.fir.declarations.declaredProperties
import org.jetbrains.kotlin.fir.declarations.getSealedClassInheritors
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
@@ -21,7 +23,9 @@ import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
+import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.resolve.transformers.WhenOnSealedClassExhaustivenessChecker.ConditionChecker.processBranch
+import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.computeRepresentativeTypeForBareType
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
@@ -447,12 +451,40 @@ private object WhenOnSealedClassExhaustivenessChecker : WhenExhaustivenessChecke
}
for (notCheckedRegularClasses in notCheckedRegularClasses) {
- destination += WhenMissingCase.IsTypeCheckIsMissing(
- notCheckedRegularClasses.classId,
- notCheckedRegularClasses.fir.classKind.isSingleton,
- notCheckedRegularClasses.ownTypeParameterSymbols.size
- )
+ if (!isUninhabited(notCheckedRegularClasses, subjectType, session)) {
+ destination += WhenMissingCase.IsTypeCheckIsMissing(
+ notCheckedRegularClasses.classId,
+ notCheckedRegularClasses.fir.classKind.isSingleton,
+ notCheckedRegularClasses.ownTypeParameterSymbols.size
+ )
+ }
+ }
+ }
+
+ private fun isUninhabited(
+ classSymbol: FirClassSymbol<*>,
+ subjectType: ConeKotlinType,
+ session: FirSession,
+ ): Boolean {
+ val classType =
+ session.computeRepresentativeTypeForBareType(classSymbol.defaultType(), subjectType) ?: return false
+ val boundsViolated =
+ checkUpperBoundViolatedNoReport(classSymbol.typeParameterSymbols, classType.typeArguments.toList(), session)
+ val containsNothing: Boolean by lazy {
+ val typeMapping =
+ classSymbol.typeParameterSymbols.zip(classType.typeArguments).mapNotNull { (parameter, arg) ->
+ when (arg) {
+ is ConeKotlinType -> parameter to arg
+ is ConeKotlinTypeProjectionOut -> parameter to arg.type
+ else -> null
+ }
+ }.toMap()
+ val substitutor = substitutorByMap(typeMapping, session)
+ val typesOfProperties = classSymbol.declaredProperties(session)
+ .map { substitutor.substituteOrSelf(it.resolvedReturnType) }
+ typesOfProperties.any { it.isNothing }
}
+ return boundsViolated || containsNothing
}
private fun inferVariantsFromSubjectSmartCast(subject: FirExpression, data: Info) {
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt
index 7a0c1fe07627f..36ac02b20f6bb 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.resolve.transformers.body.resolve
+import org.jetbrains.kotlin.fir.SessionHolder
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
@@ -16,7 +17,7 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
-fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
+fun SessionHolder.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
originalType.lowerBoundIfFlexible().fullyExpandedType().let {
if (it !== originalType) return computeRepresentativeTypeForBareType(type, it)
}
diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSession.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSession.kt
index af5a9928b63cd..d8a8e18f67487 100644
--- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSession.kt
+++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/FirSession.kt
@@ -15,7 +15,7 @@ import kotlin.reflect.KClass
abstract class FirSession @PrivateSessionConstructor constructor(
val kind: Kind
-) : ComponentArrayOwner() {
+) : ComponentArrayOwner(), SessionHolder {
companion object : ConeTypeRegistry() {
inline fun sessionComponentAccessor(): ArrayMapAccessor {
return generateAccessor(T::class)
@@ -39,6 +39,9 @@ abstract class FirSession @PrivateSessionConstructor constructor(
open val builtinTypes: BuiltinTypes = BuiltinTypes()
+ override val session: FirSession
+ get() = this
+
final override val typeRegistry: TypeRegistry = Companion
@SessionConfiguration