Skip to content

Commit

Permalink
[FIR] When subjects with dynamic types should be considered Any?
Browse files Browse the repository at this point in the history
Right now, for when exhaustiveness checking, the lower-bound of flexible
types is chosen. Dynamic types are flexible with a lower-bound of
Nothing. This leads to the subject type being Nothing and no branches
are required to be considered exhaustive. Dynamic types should instead
use their upper-bound (Any?) for exhaustive checks.

^KT-71601 Fixed
  • Loading branch information
bnorm authored and qodana-bot committed Sep 24, 2024
1 parent 2af884e commit 283de17
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
)

fun computeAllMissingCases(session: FirSession, whenExpression: FirWhenExpression): List<WhenMissingCase> {
val subjectType =
getSubjectType(session, whenExpression) ?: return ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH.reasons
val subjectType = getSubjectType(session, whenExpression)?.minimumBoundIfFlexible(session)
?: return ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH.reasons
return buildList {
for (type in subjectType.unwrapTypeParameterAndIntersectionTypes(session)) {
val checkers = getCheckers(type, session)
Expand All @@ -58,7 +58,28 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
?: whenExpression.subject?.resolvedType
?: return null

return subjectType.fullyExpandedType(session).lowerBoundIfFlexible()
return subjectType.fullyExpandedType(session)
}

/**
* The "minimum" bound of a flexible type is defined as the bound type which will be checked for exhaustion
* to determine if the when-expression is considered sufficiently exhaustive.
*
* * For [dynamic types][ConeDynamicType], this is the **upper bound**,
* because the branches must cover ***all** possible cases.
*
* * For all other [ConeFlexibleType]s, this is the **lower bound**,
* as platform types may be treated as non-null for exhaustive checks.
*/
private fun ConeKotlinType.minimumBoundIfFlexible(session: FirSession): ConeRigidType {
return when (this) {
is ConeDynamicType -> when (session.languageVersionSettings.supportsFeature(LanguageFeature.ImprovedExhaustivenessChecksIn21)) {
true -> upperBound // `dynamic` types must be exhaustive based on the upper bound (`Any?`).
false -> lowerBound
}
is ConeFlexibleType -> lowerBound // All other flexible types may be exhaustive based on the lower bound.
is ConeRigidType -> this
}
}

private fun ConeKotlinType.unwrapTypeParameterAndIntersectionTypes(session: FirSession): Collection<ConeKotlinType> {
Expand Down Expand Up @@ -123,33 +144,44 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
processExhaustivenessCheck(whenExpression)
bodyResolveComponents.session.enumWhenTracker?.reportEnumUsageInWhen(
bodyResolveComponents.file.sourceFile?.path,
getSubjectType(bodyResolveComponents.session, whenExpression)
getSubjectType(bodyResolveComponents.session, whenExpression)?.minimumBoundIfFlexible(bodyResolveComponents.session)
)
return whenExpression
}

private fun processExhaustivenessCheck(whenExpression: FirWhenExpression) {
if (whenExpression.branches.any { it.condition is FirElseIfTrueCondition }) {
if (whenExpression.hasElseBranch()) {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ProperlyExhaustive)
return
}

val session = bodyResolveComponents.session
val subjectType = getSubjectType(session, whenExpression)?.let {
session.typeApproximator.approximateToSuperType(it, TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference) ?: it
} ?: run {
val subjectType = getSubjectType(session, whenExpression)
if (subjectType == null) {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH)
return
}

if (whenExpression.branches.isEmpty() && subjectType.isNothing) {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ExhaustiveAsNothing)
return
whenExpression.replaceExhaustivenessStatus(computeExhaustivenessStatus(whenExpression, subjectType.minimumBoundIfFlexible(session)))
}

private fun FirWhenExpression.hasElseBranch(): Boolean {
return branches.any { it.condition is FirElseIfTrueCondition }
}

private fun computeExhaustivenessStatus(whenExpression: FirWhenExpression, subjectType: ConeKotlinType): ExhaustivenessStatus {
val session = bodyResolveComponents.session
val approximatedType = session.typeApproximator.approximateToSuperType(
subjectType, TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference
) ?: subjectType

if (whenExpression.branches.isEmpty() && approximatedType.isNothing) {
return ExhaustivenessStatus.ExhaustiveAsNothing
}

var status: ExhaustivenessStatus = ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH

val unwrappedIntersectionTypes = subjectType.unwrapTypeParameterAndIntersectionTypes(bodyResolveComponents.session)
val unwrappedIntersectionTypes = approximatedType.unwrapTypeParameterAndIntersectionTypes(session)

for (unwrappedSubjectType in unwrappedIntersectionTypes) {
// `kotlin.Boolean` is always exhaustive despite the fact it could be `expect` (relevant for stdlib K2)
Expand All @@ -169,7 +201,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
}
}

whenExpression.replaceExhaustivenessStatus(status)
return status
}

private fun computeStatusForNonIntersectionType(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// DIAGNOSTICS: -UNSUPPORTED
// LANGUAGE: +ImprovedExhaustivenessChecksIn21
// ISSUE: KT-71601

fun subject(): dynamic = null

fun testNoCases() {
val result = when (subject()) {
val result = <!NO_ELSE_IN_WHEN!>when<!> (subject()) {
}
}

Expand All @@ -15,7 +16,7 @@ fun testElse() {
}

fun testAny() {
val result = when (subject()) {
val result = <!NO_ELSE_IN_WHEN!>when<!> (subject()) {
is Any -> ""
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// DIAGNOSTICS: -UNSUPPORTED
// LANGUAGE: +ImprovedExhaustivenessChecksIn21
// ISSUE: KT-71601

fun subject(): dynamic = null
Expand Down

0 comments on commit 283de17

Please sign in to comment.