From c4aa6d01a97135c5e26b2f8883c170407506ec98 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Tue, 12 May 2015 16:27:05 +0300 Subject: [PATCH] Exhaustive when on boolean argument. A set of tests. Compile-time constants are taken into account. #KT-3743 Fixed. --- .../org/jetbrains/kotlin/cfg/WhenChecker.java | 69 ++++++++++++++----- .../org/jetbrains/kotlin/psi/JetPsiUtil.java | 1 + .../tests/when/ExhaustiveBoolean.kt | 8 +++ .../tests/when/ExhaustiveBoolean.txt | 3 + .../tests/when/ExhaustiveBooleanBrackets.kt | 7 ++ .../tests/when/ExhaustiveBooleanBrackets.txt | 3 + .../tests/when/ExhaustiveBooleanComplex.kt | 8 +++ .../tests/when/ExhaustiveBooleanComplex.txt | 3 + .../tests/when/ExhaustiveBooleanNullable.kt | 9 +++ .../tests/when/ExhaustiveBooleanNullable.txt | 3 + .../when/NonExhaustiveBooleanNullable.kt | 8 +++ .../when/NonExhaustiveBooleanNullable.txt | 3 + .../checkers/JetDiagnosticsTestGenerated.java | 30 ++++++++ 13 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.txt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.txt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.txt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.txt create mode 100644 compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.kt create mode 100644 compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java index ad5a296e260..262e93cc671 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java @@ -25,11 +25,13 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingTrace; +import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.TypeUtils; import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry; +import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass; public final class WhenChecker { private WhenChecker() { @@ -65,27 +67,60 @@ public final class WhenChecker { return subjectExpression == null ? null : context.getType(subjectExpression); } - private static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { - JetType type = whenSubjectType(expression, trace.getBindingContext()); - ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type); - - if (type == null || classDescriptor == null) return false; - - boolean isExhaust = true; - boolean notEmpty = false; - for (DeclarationDescriptor descriptor : classDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) { - if (isEnumEntry(descriptor)) { - notEmpty = true; - if (!containsEnumEntryCase(expression, (ClassDescriptor) descriptor, trace)) { - isExhaust = false; + private static boolean isWhenOnBooleanExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { + // It's assumed (and not checked) that expression is of the boolean type + boolean containsFalse = false; + boolean containsTrue = false; + for (JetWhenEntry whenEntry: expression.getEntries()) { + for (JetWhenCondition whenCondition : whenEntry.getConditions()) { + if (whenCondition instanceof JetWhenConditionWithExpression) { + JetExpression whenExpression = ((JetWhenConditionWithExpression) whenCondition).getExpression(); + if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true; + if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true; } } } - boolean exhaustive = isExhaust && notEmpty && (!TypeUtils.isNullableType(type) || containsNullCase(expression, trace)); - if (exhaustive) { - trace.record(BindingContext.EXHAUSTIVE_WHEN, expression); + return containsFalse && containsTrue; + } + + private static boolean isWhenOnEnumExhaustive( + @NotNull JetWhenExpression expression, @NotNull BindingTrace trace, @NotNull ClassDescriptor enumClassDescriptor) { + assert isEnumClass(enumClassDescriptor); + boolean notEmpty = false; + for (DeclarationDescriptor descriptor : enumClassDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) { + if (isEnumEntry(descriptor)) { + notEmpty = true; + if (!containsEnumEntryCase(expression, (ClassDescriptor) descriptor, trace)) { + return false; + } + } } - return exhaustive; + return notEmpty; + } + + private static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { + JetType type = whenSubjectType(expression, trace.getBindingContext()); + if (type == null) return false; + ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type); + + boolean exhaustive; + if (classDescriptor == null) { + if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) { + exhaustive = isWhenOnBooleanExhaustive(expression, trace); + } + else { + // TODO: sealed hierarchies, etc. + exhaustive = false; + } + } + else { + exhaustive = isWhenOnEnumExhaustive(expression, trace, classDescriptor); + } + if (exhaustive && (!TypeUtils.isNullableType(type) || containsNullCase(expression, trace))) { + trace.record(BindingContext.EXHAUSTIVE_WHEN, expression); + return true; + } + return false; } private static boolean containsEnumEntryCase( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java index ba5d664b652..38f8478b945 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPsiUtil.java @@ -36,6 +36,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.JetNodeTypes; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.kdoc.psi.api.KDocElement; +import org.jetbrains.kotlin.lexer.JetKeywordToken; import org.jetbrains.kotlin.lexer.JetToken; import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.name.FqName; diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.kt new file mode 100644 index 00000000000..fc65d061fce --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.kt @@ -0,0 +1,8 @@ +// See also: KT-3743 +fun foo(arg: Boolean): String { + // Must be exhaustive + return when(arg) { + true -> "truth" + false -> "falsehood" + } +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.txt new file mode 100644 index 00000000000..3a39820aed7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.txt @@ -0,0 +1,3 @@ +package + +internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.kt new file mode 100644 index 00000000000..3ac5e9812b7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.kt @@ -0,0 +1,7 @@ +fun foo(arg: Boolean): String { + // Must be exhaustive + return when(arg) { + (true) -> "truth" + ((false)) -> "falsehood" + } +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.txt new file mode 100644 index 00000000000..3a39820aed7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.txt @@ -0,0 +1,3 @@ +package + +internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.kt new file mode 100644 index 00000000000..43d4890652b --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.kt @@ -0,0 +1,8 @@ +// See also: KT-3743 +fun foo(arg: Boolean): String { + // Must be exhaustive + return when(arg) { + 2 == 2 -> "truth" + 2 == 1 -> "falsehood" + } +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.txt new file mode 100644 index 00000000000..3a39820aed7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.txt @@ -0,0 +1,3 @@ +package + +internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.kt new file mode 100644 index 00000000000..dfb530e9641 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.kt @@ -0,0 +1,9 @@ +// See also: KT-3743 +fun foo(arg: Boolean?): String { + // Must be exhaustive + return when(arg) { + true -> "truth" + false -> "falsehood" + null -> "unknown" + } +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.txt new file mode 100644 index 00000000000..8c6b41d99d8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.txt @@ -0,0 +1,3 @@ +package + +internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.String diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.kt new file mode 100644 index 00000000000..dfef6062c44 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.kt @@ -0,0 +1,8 @@ +// See also: KT-3743 +fun foo(arg: Boolean?): String { + // Must be NOT exhaustive + return when(arg) { + true -> "truth" + false -> "falsehood" + } +} diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.txt b/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.txt new file mode 100644 index 00000000000..8c6b41d99d8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.txt @@ -0,0 +1,3 @@ +package + +internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.String diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 4ef9561bb0c..3afd052a6b0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -12897,6 +12897,30 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("ExhaustiveBoolean.kt") + public void testExhaustiveBoolean() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveBoolean.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveBooleanBrackets.kt") + public void testExhaustiveBooleanBrackets() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveBooleanBrackets.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveBooleanComplex.kt") + public void testExhaustiveBooleanComplex() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveBooleanComplex.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveBooleanNullable.kt") + public void testExhaustiveBooleanNullable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveBooleanNullable.kt"); + doTest(fileName); + } + @TestMetadata("kt4434.kt") public void testKt4434() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt"); @@ -12945,6 +12969,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("NonExhaustiveBooleanNullable.kt") + public void testNonExhaustiveBooleanNullable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/NonExhaustiveBooleanNullable.kt"); + doTest(fileName); + } + @TestMetadata("When.kt") public void testWhen() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/When.kt");