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 cf03a173479..c9c801eab27 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 @@ -31031,6 +31031,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckElse.kt"); } + @Test + @TestMetadata("intersectionExhaustivenessComplex.kt") + public void testIntersectionExhaustivenessComplex() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt"); + } + + @Test + @TestMetadata("intersectionExhaustivenessSimple.kt") + public void testIntersectionExhaustivenessSimple() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt"); + } + @Test @TestMetadata("kt10439.kt") public void testKt10439() 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 618492140c1..98101e7e268 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 @@ -31031,6 +31031,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckElse.kt"); } + @Test + @TestMetadata("intersectionExhaustivenessComplex.kt") + public void testIntersectionExhaustivenessComplex() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt"); + } + + @Test + @TestMetadata("intersectionExhaustivenessSimple.kt") + public void testIntersectionExhaustivenessSimple() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt"); + } + @Test @TestMetadata("kt10439.kt") public void testKt10439() throws Exception { 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 abf4ff8395c..01f03c387be 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 @@ -18,13 +18,13 @@ import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.symbolProvider import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol -import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.visitors.FirTransformer import org.jetbrains.kotlin.fir.visitors.FirVisitor +import org.jetbrains.kotlin.name.StandardClassIds class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyResolveComponents) : FirTransformer() { companion object { @@ -60,32 +60,57 @@ class FirWhenExhaustivenessTransformer(private val bodyResolveComponents: BodyRe val session = bodyResolveComponents.session val cleanSubjectType = subjectType.fullyExpandedType(session).lowerBoundIfFlexible() + val unwrappedIntersectionTypes = (cleanSubjectType as? ConeIntersectionType)?.intersectedTypes ?: listOf(cleanSubjectType) - val checkers = buildList { - exhaustivenessCheckers.filterTo(this) { it.isApplicable(cleanSubjectType, session) } - if (isNotEmpty() && cleanSubjectType.isMarkedNullable) { + + var status: ExhaustivenessStatus = ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH + + for (unwrappedSubjectType in unwrappedIntersectionTypes) { + val localStatus = computeStatusForNonIntersectionType(unwrappedSubjectType, session, whenExpression) + when { + localStatus === ExhaustivenessStatus.Exhaustive -> { + status = localStatus + break + } + localStatus !== ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH && status === ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH -> { + status = localStatus + } + } + } + + whenExpression.replaceExhaustivenessStatus(status) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun computeStatusForNonIntersectionType( + unwrappedSubjectType: ConeKotlinType, + session: FirSession, + whenExpression: FirWhenExpression, + ): ExhaustivenessStatus { + val checkers = buildList { + exhaustivenessCheckers.filterTo(this) { it.isApplicable(unwrappedSubjectType, session) } + if (isNotEmpty() && unwrappedSubjectType.isMarkedNullable) { add(WhenOnNullableExhaustivenessChecker) } } if (checkers.isEmpty()) { - whenExpression.replaceExhaustivenessStatus(ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH) - return + return ExhaustivenessStatus.NotExhaustive.NO_ELSE_BRANCH } + val whenMissingCases = mutableListOf() for (checker in checkers) { - checker.computeMissingCases(whenExpression, cleanSubjectType, session, whenMissingCases) + checker.computeMissingCases(whenExpression, unwrappedSubjectType, session, whenMissingCases) } if (whenMissingCases.isEmpty() && whenExpression.branches.isEmpty()) { whenMissingCases.add(WhenMissingCase.Unknown) } - val status = if (whenMissingCases.isEmpty()) { + return if (whenMissingCases.isEmpty()) { ExhaustivenessStatus.Exhaustive } else { ExhaustivenessStatus.NotExhaustive(whenMissingCases) } - whenExpression.replaceExhaustivenessStatus(status) } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt27221_irrelevantClasses.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/kt27221_irrelevantClasses.fir.kt index 51270bfde33..eed5bf1118f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/kt27221_irrelevantClasses.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/kt27221_irrelevantClasses.fir.kt @@ -10,7 +10,7 @@ object CC : C() fun foo(a: A) { if (a is B) { if (a is C) { - val t = when (a) { + val t = when (a) { is CC -> "CC" } } @@ -20,7 +20,7 @@ fun foo(a: A) { fun foo2(a: A) { if (a is C) { if (a is B) { - val t = when (a) { + val t = when (a) { is CC -> "CC" } } diff --git a/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.fir.kt b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.fir.kt new file mode 100644 index 00000000000..77c5cabe55f --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.fir.kt @@ -0,0 +1,42 @@ +// !CHECK_TYPE + +sealed class A { + class A1 : A() + class A2 : A() +} + +sealed class B { + class B1 : B() + class B2 : B() +} + +fun foo(a: A) { + if (a !is B) return + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + }.length + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + }.length // OK + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + is B.B1 -> "..." // should be warning: unreachable code + }.length // OK + + when (a) { + is A.A1 -> "" + is B.B1 -> "..." + is A.A2 -> "v" + }.length // OK + + when (a) { + is A.A1 -> "" + is B.B1 -> "..." + }.length +} diff --git a/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt new file mode 100644 index 00000000000..3c2ad18612a --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt @@ -0,0 +1,42 @@ +// !CHECK_TYPE + +sealed class A { + class A1 : A() + class A2 : A() +} + +sealed class B { + class B1 : B() + class B2 : B() +} + +fun foo(a: A) { + if (a !is B) return + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + }.length + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + }.length // OK + + when (a) { + is A.A1 -> "" + is A.A2 -> "v" + is B.B1 -> "..." // should be warning: unreachable code + }.length // OK + + when (a) { + is A.A1 -> "" + is B.B1 -> "..." + is A.A2 -> "v" + }.length // OK + + when (a) { + is A.A1 -> "" + is B.B1 -> "..." + }.length +} diff --git a/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.txt b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.txt new file mode 100644 index 00000000000..2f8cafa692b --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.txt @@ -0,0 +1,45 @@ +package + +public fun foo(/*0*/ a: A): kotlin.Unit + +public sealed class A { + protected constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class A1 : A { + public constructor A1() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class A2 : A { + public constructor A2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public sealed class B { + protected constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class B1 : B { + public constructor B1() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class B2 : B { + public constructor B2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt new file mode 100644 index 00000000000..c98188665e1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt @@ -0,0 +1,21 @@ +// SKIP_TXT +// FIR_IDENTICAL +// !CHECK_TYPE + +sealed class KtClassifierSymbol + +interface KtNamedSymbol + +abstract class KtTypeParameterSymbol : KtClassifierSymbol() {} + +sealed class KtClassLikeSymbol : KtClassifierSymbol() {} + +fun foo(symbol: KtClassifierSymbol) { + if (symbol !is KtNamedSymbol) return + val x = when (symbol) { + is KtClassLikeSymbol -> "1" + is KtTypeParameterSymbol -> "2" + } + + x checkType { _() } +} diff --git a/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.txt b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.txt new file mode 100644 index 00000000000..3aa3a134538 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.txt @@ -0,0 +1,30 @@ +package + +public fun foo(/*0*/ symbol: KtClassifierSymbol): kotlin.Unit + +public sealed class KtClassLikeSymbol : KtClassifierSymbol { + protected constructor KtClassLikeSymbol() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public sealed class KtClassifierSymbol { + protected constructor KtClassifierSymbol() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface KtNamedSymbol { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class KtTypeParameterSymbol : KtClassifierSymbol { + public constructor KtTypeParameterSymbol() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} 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 62a23711352..bb118968603 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 @@ -31127,6 +31127,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckElse.kt"); } + @Test + @TestMetadata("intersectionExhaustivenessComplex.kt") + public void testIntersectionExhaustivenessComplex() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt"); + } + + @Test + @TestMetadata("intersectionExhaustivenessSimple.kt") + public void testIntersectionExhaustivenessSimple() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt"); + } + @Test @TestMetadata("kt10439.kt") public void testKt10439() 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 6375a14a57c..55bcfd55cb5 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 @@ -27161,6 +27161,16 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckElse.kt"); } + @TestMetadata("intersectionExhaustivenessComplex.kt") + public void testIntersectionExhaustivenessComplex() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessComplex.kt"); + } + + @TestMetadata("intersectionExhaustivenessSimple.kt") + public void testIntersectionExhaustivenessSimple() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/intersectionExhaustivenessSimple.kt"); + } + @TestMetadata("kt10439.kt") public void testKt10439() throws Exception { runTest("compiler/testData/diagnostics/tests/when/kt10439.kt");