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");
|
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
|
@Test
|
||||||
@TestMetadata("when.kt234.kt973.kt")
|
@TestMetadata("when.kt234.kt973.kt")
|
||||||
public void testWhen_kt234_kt973() throws Exception {
|
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");
|
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
|
@Test
|
||||||
@TestMetadata("when.kt234.kt973.kt")
|
@TestMetadata("when.kt234.kt973.kt")
|
||||||
public void testWhen_kt234_kt973() throws Exception {
|
public void testWhen_kt234_kt973() throws Exception {
|
||||||
|
|||||||
@@ -646,7 +646,7 @@ class Fir2IrVisitor(
|
|||||||
it.condition !is FirElseIfTrueCondition || it.result.statements.isNotEmpty()
|
it.condition !is FirElseIfTrueCondition || it.result.statements.isNotEmpty()
|
||||||
}?.toIrWhenBranch(whenExpression.typeRef)
|
}?.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(
|
val irResult = IrCallImpl(
|
||||||
startOffset, endOffset, irBuiltIns.nothingType,
|
startOffset, endOffset, irBuiltIns.nothingType,
|
||||||
irBuiltIns.noWhenBranchMatchedExceptionSymbol,
|
irBuiltIns.noWhenBranchMatchedExceptionSymbol,
|
||||||
@@ -660,7 +660,7 @@ class Fir2IrVisitor(
|
|||||||
generateWhen(
|
generateWhen(
|
||||||
startOffset, endOffset, origin,
|
startOffset, endOffset, origin,
|
||||||
subjectVariable, irBranches,
|
subjectVariable, irBranches,
|
||||||
if (whenExpression.isExhaustive && whenExpression.branches.none {
|
if (whenExpression.isProperlyExhaustive && whenExpression.branches.none {
|
||||||
it.condition is FirElseIfTrueCondition && it.result.statements.isEmpty()
|
it.condition is FirElseIfTrueCondition && it.result.statements.isEmpty()
|
||||||
}
|
}
|
||||||
) whenExpression.typeRef.toIrType() else irBuiltIns.unitType
|
) 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) {
|
class WhenSyntheticElseBranchNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int, id: Int) : CFGNode<FirWhenExpression>(owner, level, id) {
|
||||||
init {
|
init {
|
||||||
assert(!fir.isExhaustive)
|
assert(!fir.isProperlyExhaustive)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
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
|
// exit from last condition node still on stack
|
||||||
// we should remove it
|
// we should remove it
|
||||||
val lastWhenConditionExit = lastNodes.pop()
|
val lastWhenConditionExit = lastNodes.pop()
|
||||||
val syntheticElseBranchNode = if (!whenExpression.isExhaustive) {
|
val syntheticElseBranchNode = if (!whenExpression.isProperlyExhaustive) {
|
||||||
createWhenSyntheticElseBranchNode(whenExpression).apply {
|
createWhenSyntheticElseBranchNode(whenExpression).apply {
|
||||||
addEdge(lastWhenConditionExit, this)
|
addEdge(lastWhenConditionExit, this)
|
||||||
addEdge(this, whenExitNode)
|
addEdge(this, whenExitNode)
|
||||||
|
|||||||
+14
-9
@@ -47,20 +47,25 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
|
|||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private fun processExhaustivenessCheck(whenExpression: FirWhenExpression) {
|
private fun processExhaustivenessCheck(whenExpression: FirWhenExpression) {
|
||||||
if (whenExpression.branches.any { it.condition is FirElseIfTrueCondition }) {
|
if (whenExpression.branches.any { it.condition is FirElseIfTrueCondition }) {
|
||||||
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.Exhaustive)
|
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ProperlyExhaustive)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val subjectType = whenExpression.subjectVariable?.returnTypeRef?.coneType
|
val session = bodyResolveComponents.session
|
||||||
?: whenExpression.subject?.typeRef?.coneType
|
val subjectType = (whenExpression.subjectVariable?.returnTypeRef?.coneType
|
||||||
|
?: whenExpression.subject?.typeRef?.coneType)
|
||||||
|
?.fullyExpandedType(session)?.lowerBoundIfFlexible()
|
||||||
?: run {
|
?: run {
|
||||||
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH)
|
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val session = bodyResolveComponents.session
|
if (whenExpression.branches.isEmpty() && subjectType.isNothing) {
|
||||||
val cleanSubjectType = subjectType.fullyExpandedType(session).lowerBoundIfFlexible()
|
whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.ExhaustiveAsNothing)
|
||||||
val unwrappedIntersectionTypes = (cleanSubjectType as? ConeIntersectionType)?.intersectedTypes ?: listOf(cleanSubjectType)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val unwrappedIntersectionTypes = (subjectType as? ConeIntersectionType)?.intersectedTypes ?: listOf(subjectType)
|
||||||
|
|
||||||
|
|
||||||
var status: ExhaustivenessStatus = ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH
|
var status: ExhaustivenessStatus = ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH
|
||||||
@@ -68,7 +73,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
|
|||||||
for (unwrappedSubjectType in unwrappedIntersectionTypes) {
|
for (unwrappedSubjectType in unwrappedIntersectionTypes) {
|
||||||
val localStatus = computeStatusForNonIntersectionType(unwrappedSubjectType, session, whenExpression)
|
val localStatus = computeStatusForNonIntersectionType(unwrappedSubjectType, session, whenExpression)
|
||||||
when {
|
when {
|
||||||
localStatus === ExhaustivenessStatus.Exhaustive -> {
|
localStatus === ExhaustivenessStatus.ProperlyExhaustive -> {
|
||||||
status = localStatus
|
status = localStatus
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -87,7 +92,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
|
|||||||
session: FirSession,
|
session: FirSession,
|
||||||
whenExpression: FirWhenExpression,
|
whenExpression: FirWhenExpression,
|
||||||
): ExhaustivenessStatus {
|
): ExhaustivenessStatus {
|
||||||
val checkers = buildList<WhenExhaustivenessChecker> {
|
val checkers = buildList {
|
||||||
exhaustivenessCheckers.filterTo(this) { it.isApplicable(unwrappedSubjectType, session) }
|
exhaustivenessCheckers.filterTo(this) { it.isApplicable(unwrappedSubjectType, session) }
|
||||||
if (isNotEmpty() && unwrappedSubjectType.isMarkedNullable) {
|
if (isNotEmpty() && unwrappedSubjectType.isMarkedNullable) {
|
||||||
add(WhenOnNullableExhaustivenessChecker)
|
add(WhenOnNullableExhaustivenessChecker)
|
||||||
@@ -107,7 +112,7 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
return if (whenMissingCases.isEmpty()) {
|
return if (whenMissingCases.isEmpty()) {
|
||||||
ExhaustivenessStatus.Exhaustive
|
ExhaustivenessStatus.ProperlyExhaustive
|
||||||
} else {
|
} else {
|
||||||
ExhaustivenessStatus.NotExhaustive(whenMissingCases)
|
ExhaustivenessStatus.NotExhaustive(whenMissingCases)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirBodyResolveTran
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun FirWhenExpression.replaceReturnTypeIfNotExhaustive(): FirWhenExpression {
|
private fun FirWhenExpression.replaceReturnTypeIfNotExhaustive(): FirWhenExpression {
|
||||||
if (!isExhaustive) {
|
if (!isProperlyExhaustive) {
|
||||||
resultType = resultType.resolvedTypeFromPrototype(session.builtinTypes.unitType.type)
|
resultType = resultType.resolvedTypeFromPrototype(session.builtinTypes.unitType.type)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
|
|||||||
@@ -8,7 +8,19 @@ package org.jetbrains.kotlin.fir.expressions
|
|||||||
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
|
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
|
||||||
|
|
||||||
sealed class ExhaustivenessStatus {
|
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() {
|
class NotExhaustive(val reasons: List<WhenMissingCase>) : ExhaustivenessStatus() {
|
||||||
companion object {
|
companion object {
|
||||||
val NO_ELSE_BRANCH = NotExhaustive(listOf(WhenMissingCase.Unknown))
|
val NO_ELSE_BRANCH = NotExhaustive(listOf(WhenMissingCase.Unknown))
|
||||||
@@ -18,4 +30,7 @@ sealed class ExhaustivenessStatus {
|
|||||||
|
|
||||||
|
|
||||||
val FirWhenExpression.isExhaustive: Boolean
|
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");
|
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
|
@Test
|
||||||
@TestMetadata("when.kt234.kt973.kt")
|
@TestMetadata("when.kt234.kt973.kt")
|
||||||
public void testWhen_kt234_kt973() throws Exception {
|
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");
|
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
|
@Test
|
||||||
@TestMetadata("when.kt234.kt973.kt")
|
@TestMetadata("when.kt234.kt973.kt")
|
||||||
public void testWhen_kt234_kt973() throws Exception {
|
public void testWhen_kt234_kt973() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user