FIR: accept when(nothing) {} as exhaustive
FE1.0 accepts this but FIR current rejects it.
This commit is contained in:
committed by
Ilya Kirillov
parent
26e3237b8c
commit
6ec247b861
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+14
-9
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
+9
@@ -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) {}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package
|
||||
|
||||
public fun foo(/*0*/ n: kotlin.Nothing, /*1*/ n2: MyNothing /* = kotlin.Nothing */): kotlin.Unit
|
||||
public typealias MyNothing = kotlin.Nothing
|
||||
Generated
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user