FIR: accept when(nothing) {} as exhaustive

FE1.0 accepts this but FIR current rejects it.
This commit is contained in:
Tianyu Geng
2021-06-07 20:16:29 -07:00
committed by Ilya Kirillov
parent 26e3237b8c
commit 6ec247b861
12 changed files with 73 additions and 16 deletions
@@ -5661,6 +5661,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt");
}
@Test
@TestMetadata("whenWithNothingTypedSubject.kt")
public void testWhenWithNothingTypedSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt");
}
@Test
@TestMetadata("when.kt234.kt973.kt")
public void testWhen_kt234_kt973() throws Exception {
@@ -5661,6 +5661,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt");
}
@Test
@TestMetadata("whenWithNothingTypedSubject.kt")
public void testWhenWithNothingTypedSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt");
}
@Test
@TestMetadata("when.kt234.kt973.kt")
public void testWhen_kt234_kt973() throws Exception {
@@ -646,7 +646,7 @@ class Fir2IrVisitor(
it.condition !is FirElseIfTrueCondition || it.result.statements.isNotEmpty()
}?.toIrWhenBranch(whenExpression.typeRef)
}
if (whenExpression.isExhaustive && whenExpression.branches.none { it.condition is FirElseIfTrueCondition }) {
if (whenExpression.isProperlyExhaustive && whenExpression.branches.none { it.condition is FirElseIfTrueCondition }) {
val irResult = IrCallImpl(
startOffset, endOffset, irBuiltIns.nothingType,
irBuiltIns.noWhenBranchMatchedExceptionSymbol,
@@ -660,7 +660,7 @@ class Fir2IrVisitor(
generateWhen(
startOffset, endOffset, origin,
subjectVariable, irBranches,
if (whenExpression.isExhaustive && whenExpression.branches.none {
if (whenExpression.isProperlyExhaustive && whenExpression.branches.none {
it.condition is FirElseIfTrueCondition && it.result.statements.isEmpty()
}
) whenExpression.typeRef.toIrType() else irBuiltIns.unitType
@@ -369,7 +369,7 @@ class WhenBranchResultExitNode(owner: ControlFlowGraph, override val fir: FirWhe
}
class WhenSyntheticElseBranchNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int, id: Int) : CFGNode<FirWhenExpression>(owner, level, id) {
init {
assert(!fir.isExhaustive)
assert(!fir.isProperlyExhaustive)
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
@@ -631,7 +631,7 @@ class ControlFlowGraphBuilder {
// exit from last condition node still on stack
// we should remove it
val lastWhenConditionExit = lastNodes.pop()
val syntheticElseBranchNode = if (!whenExpression.isExhaustive) {
val syntheticElseBranchNode = if (!whenExpression.isProperlyExhaustive) {
createWhenSyntheticElseBranchNode(whenExpression).apply {
addEdge(lastWhenConditionExit, this)
addEdge(this, whenExitNode)
@@ -47,20 +47,25 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
@OptIn(ExperimentalStdlibApi::class)
private fun processExhaustivenessCheck(whenExpression: FirWhenExpression) {
if (whenExpression.branches.any { it.condition is FirElseIfTrueCondition }) {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.Exhaustive)
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ProperlyExhaustive)
return
}
val subjectType = whenExpression.subjectVariable?.returnTypeRef?.coneType
?: whenExpression.subject?.typeRef?.coneType
val session = bodyResolveComponents.session
val subjectType = (whenExpression.subjectVariable?.returnTypeRef?.coneType
?: whenExpression.subject?.typeRef?.coneType)
?.fullyExpandedType(session)?.lowerBoundIfFlexible()
?: run {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH)
return
}
val session = bodyResolveComponents.session
val cleanSubjectType = subjectType.fullyExpandedType(session).lowerBoundIfFlexible()
val unwrappedIntersectionTypes = (cleanSubjectType as? ConeIntersectionType)?.intersectedTypes ?: listOf(cleanSubjectType)
if (whenExpression.branches.isEmpty() && subjectType.isNothing) {
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ExhaustiveAsNothing)
return
}
val unwrappedIntersectionTypes = (subjectType as? ConeIntersectionType)?.intersectedTypes ?: listOf(subjectType)
var status: ExhaustivenessStatus = ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH
@@ -68,7 +73,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
for (unwrappedSubjectType in unwrappedIntersectionTypes) {
val localStatus = computeStatusForNonIntersectionType(unwrappedSubjectType, session, whenExpression)
when {
localStatus === ExhaustivenessStatus.Exhaustive -> {
localStatus === ExhaustivenessStatus.ProperlyExhaustive -> {
status = localStatus
break
}
@@ -87,7 +92,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
session: FirSession,
whenExpression: FirWhenExpression,
): ExhaustivenessStatus {
val checkers = buildList<WhenExhaustivenessChecker> {
val checkers = buildList {
exhaustivenessCheckers.filterTo(this) { it.isApplicable(unwrappedSubjectType, session) }
if (isNotEmpty() && unwrappedSubjectType.isMarkedNullable) {
add(WhenOnNullableExhaustivenessChecker)
@@ -107,7 +112,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
}
return if (whenMissingCases.isEmpty()) {
ExhaustivenessStatus.Exhaustive
ExhaustivenessStatus.ProperlyExhaustive
} else {
ExhaustivenessStatus.NotExhaustive(whenMissingCases)
}
@@ -96,7 +96,7 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirBodyResolveTran
}
private fun FirWhenExpression.replaceReturnTypeIfNotExhaustive(): FirWhenExpression {
if (!isExhaustive) {
if (!isProperlyExhaustive) {
resultType = resultType.resolvedTypeFromPrototype(session.builtinTypes.unitType.type)
}
return this
@@ -8,7 +8,19 @@ package org.jetbrains.kotlin.fir.expressions
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
sealed class ExhaustivenessStatus {
object Exhaustive : ExhaustivenessStatus()
/**
* This value is used if the subject has type other than `Nothing`, in which case it's literally exhaustive only if type's possible
* cases are properly covered.
*/
object ProperlyExhaustive : ExhaustivenessStatus()
/**
* This value is used if the subject has type `Nothing`, in which case even an empty `when` is considered exhaustive. Also, in this
* case, a synthetic else branch is created.
*/
object ExhaustiveAsNothing : ExhaustivenessStatus()
class NotExhaustive(val reasons: List<WhenMissingCase>) : ExhaustivenessStatus() {
companion object {
val NO_ELSE_BRANCH = NotExhaustive(listOf(WhenMissingCase.Unknown))
@@ -18,4 +30,7 @@ sealed class ExhaustivenessStatus {
val FirWhenExpression.isExhaustive: Boolean
get() = exhaustivenessStatus == ExhaustivenessStatus.Exhaustive
get() = exhaustivenessStatus == ExhaustivenessStatus.ProperlyExhaustive || exhaustivenessStatus == ExhaustivenessStatus.ExhaustiveAsNothing
val FirWhenExpression.isProperlyExhaustive: Boolean
get() = exhaustivenessStatus == ExhaustivenessStatus.ProperlyExhaustive
@@ -0,0 +1,9 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNREACHABLE_CODE
typealias MyNothing = Nothing
fun foo(n: Nothing, n2: MyNothing) {
val a: Unit = when(n) {}
val b: Unit = when(n2) {}
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ n: kotlin.Nothing, /*1*/ n2: MyNothing /* = kotlin.Nothing */): kotlin.Unit
public typealias MyNothing = kotlin.Nothing
@@ -5667,6 +5667,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt");
}
@Test
@TestMetadata("whenWithNothingTypedSubject.kt")
public void testWhenWithNothingTypedSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt");
}
@Test
@TestMetadata("when.kt234.kt973.kt")
public void testWhen_kt234_kt973() throws Exception {
@@ -5661,6 +5661,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt");
}
@Test
@TestMetadata("whenWithNothingTypedSubject.kt")
public void testWhenWithNothingTypedSubject() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt");
}
@Test
@TestMetadata("when.kt234.kt973.kt")
public void testWhen_kt234_kt973() throws Exception {