Exhaustive when on boolean argument. A set of tests.

Compile-time constants are taken into account. #KT-3743 Fixed.
This commit is contained in:
Mikhail Glukhikh
2015-05-12 16:27:05 +03:00
parent 633b252ff5
commit c4aa6d01a9
13 changed files with 138 additions and 17 deletions
@@ -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(
@@ -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;
@@ -0,0 +1,8 @@
// See also: KT-3743
fun foo(arg: Boolean): String {
// Must be exhaustive
return when(arg) {
true -> "truth"
false -> "falsehood"
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String
@@ -0,0 +1,7 @@
fun foo(arg: Boolean): String {
// Must be exhaustive
return when(arg) {
(true) -> "truth"
((false)) -> "falsehood"
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String
@@ -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"
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean): kotlin.String
@@ -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"
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.String
@@ -0,0 +1,8 @@
// See also: KT-3743
fun foo(arg: Boolean?): String {
// Must be NOT exhaustive
return <!NO_ELSE_IN_WHEN!>when<!>(arg) {
true -> "truth"
false -> "falsehood"
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.String
@@ -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");