diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 995ee6736aa..a51c3743dfa 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -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 { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index d0dc46c12bc..cf981a6390f 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -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 { diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt index 8b90d049bb8..ea09fc078b2 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt @@ -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 diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt index 4b6d8281490..1ae6c7bc74b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt @@ -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(owner, level, id) { init { - assert(!fir.isExhaustive) + assert(!fir.isProperlyExhaustive) } override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index fea7e1485cc..0d79cb5b382 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -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) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessTransformer.kt index 01f03c387be..e75f378aa86 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/FirWhenExhaustivenessTransformer.kt @@ -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 { + 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) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt index bc0b2d1b22a..261386edb51 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt @@ -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 diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/ExhaustivenessStatus.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/ExhaustivenessStatus.kt index 85693f0fdec..1002db8b50b 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/ExhaustivenessStatus.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/ExhaustivenessStatus.kt @@ -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) : 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 \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt b/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt new file mode 100644 index 00000000000..c491c2355ac --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.kt @@ -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) {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.txt b/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.txt new file mode 100644 index 00000000000..272d8cc9910 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/whenWithNothingTypedSubject.txt @@ -0,0 +1,4 @@ +package + +public fun foo(/*0*/ n: kotlin.Nothing, /*1*/ n2: MyNothing /* = kotlin.Nothing */): kotlin.Unit +public typealias MyNothing = kotlin.Nothing diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 07c09f32c6a..2984137331b 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -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 { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index eecba3457df..5a613a801e5 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -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 {