diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index a942ec57e80..538538e6f37 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -32929,6 +32929,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt"); } + @Test + @TestMetadata("exhaustiveWhenWithConstVal.kt") + public void testExhaustiveWhenWithConstVal() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt"); + } + @Test @TestMetadata("ExhaustiveWithNullabilityCheck.kt") public void testExhaustiveWithNullabilityCheck() throws Exception { 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 fa35b37dfd6..8774a7c9aaa 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 @@ -32929,6 +32929,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt"); } + @Test + @TestMetadata("exhaustiveWhenWithConstVal.kt") + public void testExhaustiveWhenWithConstVal() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt"); + } + @Test @TestMetadata("ExhaustiveWithNullabilityCheck.kt") public void testExhaustiveWithNullabilityCheck() 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 0e082924fb2..95d67e24f6f 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 @@ -32929,6 +32929,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt"); } + @Test + @TestMetadata("exhaustiveWhenWithConstVal.kt") + public void testExhaustiveWhenWithConstVal() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt"); + } + @Test @TestMetadata("ExhaustiveWithNullabilityCheck.kt") public void testExhaustiveWithNullabilityCheck() throws Exception { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index 3c487e84f00..9b5bb118ff0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -361,7 +361,30 @@ object WhenChecker { if (expression.subjectExpression == null) return val checkedTypes = HashSet>() - val checkedConstants = HashSet>() + /* + * `true` in map means that constant can be removed and nothing breaks + * `false` means opposite + * + * Example: + * const val myF = false + * const val myT = true + * + * fun test_1(someBoolean: Boolean) { + * val s = when (someBoolean) { + * myT -> 1 + * myF -> 2 + * true -> 3 // DUPLICATE_LABEL_IN_WHEN + * false -> 4 // DUPLICATE_LABEL_IN_WHEN + * } + * } + * + * In this case myT and myF actually are `true` and `false` correspondingly, but + * const vals are not treated by exhaustive checkers, so removal `true` or `false` + * branches will break code, so we need to report DUPLICATE_LABEL_IN_WHEN on `myT` and + * `myF`, not on `true` and `false` + */ + val checkedConstants = mutableMapOf, Boolean>() + val notTrivialBranches = mutableMapOf, KtExpression>() for (entry in expression.entries) { if (entry.isElse) continue @@ -372,10 +395,37 @@ object WhenChecker { val constant = ConstantExpressionEvaluator.getConstant( constantExpression, trace.bindingContext ) ?: continue@conditions - if (checkedConstants.contains(constant)) { - trace.report(Errors.DUPLICATE_LABEL_IN_WHEN.on(constantExpression)) - } else { - checkedConstants.add(constant) + + fun report(reportOn: KtExpression) { + trace.report(Errors.DUPLICATE_LABEL_IN_WHEN.on(reportOn)) + } + + when (checkedConstants[constant]) { + true -> { + // already found trivial constant in previous branches + report(constantExpression) + } + false -> { + // already found bad constant in previous branches + val isTrivial = constant.isTrivial() + if (isTrivial) { + // this constant is trivial -> report on first non trivial constant + val reportOn = notTrivialBranches.remove(constant)!! + report(reportOn) + checkedConstants[constant] = true + } else { + // this constant is also not trivial -> report on it + report(constantExpression) + } + } + null -> { + // met constant for a first time + val isTrivial = constant.isTrivial() + checkedConstants[constant] = isTrivial + if (!isTrivial) { + notTrivialBranches[constant] = constantExpression + } + } } } @@ -394,7 +444,10 @@ object WhenChecker { } } } + } + private fun CompileTimeConstant<*>.isTrivial(): Boolean { + return !usesVariableAsConstant } fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) { diff --git a/compiler/testData/diagnostics/tests/when/When.kt b/compiler/testData/diagnostics/tests/when/When.kt index a04cb887c8f..67a5d94fb0e 100644 --- a/compiler/testData/diagnostics/tests/when/When.kt +++ b/compiler/testData/diagnostics/tests/when/When.kt @@ -40,8 +40,8 @@ fun test() { val s = ""; when (x) { - s -> 1 - "" -> 1 + s -> 1 + "" -> 1 x -> 1 1 -> 1 } diff --git a/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.fir.kt b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.fir.kt new file mode 100644 index 00000000000..c78f2df8c41 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.fir.kt @@ -0,0 +1,45 @@ +// ISSUE: KT-50385 + +const val myF = false +const val myT = true + +fun test_1(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myF /* false */ -> 2 + true -> 3 + false -> 4 + } +} + +fun test_2(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + true -> 2 + false -> 3 + } +} + +fun test_3(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + false -> 2 + } +} + +fun test_4(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myT /* true */ -> 2 + false -> 3 + } +} + +fun test_5(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myT /* true */ -> 2 + myT /* true */ -> 3 + false -> 4 + } +} diff --git a/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt new file mode 100644 index 00000000000..cd4929dcf12 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt @@ -0,0 +1,46 @@ +// ISSUE: KT-50385 + +const val myF = false +const val myT = true + +fun test_1(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myF /* false */ -> 2 + true -> 3 + false -> 4 + } +} + +fun test_2(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + true -> 2 + false -> 3 + } +} + +fun test_3(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + false -> 2 + } +} + +fun test_4(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myT /* true */ -> 2 + false -> 3 + } +} + +fun test_5(someBoolean: Boolean) { + val s = when (someBoolean) { + myT /* true */ -> 1 + myT /* true */ -> 2 + myT /* true */ -> 3 + false -> 4 + } +} + diff --git a/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.txt b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.txt new file mode 100644 index 00000000000..caf7de6b080 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.txt @@ -0,0 +1,10 @@ +package + +public const val myF: kotlin.Boolean = false +public const val myT: kotlin.Boolean = true +public fun test_1(/*0*/ someBoolean: kotlin.Boolean): kotlin.Unit +public fun test_2(/*0*/ someBoolean: kotlin.Boolean): kotlin.Unit +public fun test_3(/*0*/ someBoolean: kotlin.Boolean): kotlin.Unit +public fun test_4(/*0*/ someBoolean: kotlin.Boolean): kotlin.Unit +public fun test_5(/*0*/ someBoolean: kotlin.Boolean): kotlin.Unit + 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 3fadfd7128c..ca12e1cb393 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 @@ -33025,6 +33025,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt"); } + @Test + @TestMetadata("exhaustiveWhenWithConstVal.kt") + public void testExhaustiveWhenWithConstVal() throws Exception { + runTest("compiler/testData/diagnostics/tests/when/exhaustiveWhenWithConstVal.kt"); + } + @Test @TestMetadata("ExhaustiveWithNullabilityCheck.kt") public void testExhaustiveWithNullabilityCheck() throws Exception {