From e1d3b296e905a2079612c473952b4a5b8641300f Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 29 Jun 2015 13:55:08 +0300 Subject: [PATCH] Exhaustive when over enum may now use "is" instead of comparison + a pair of tests --- .../org/jetbrains/kotlin/cfg/WhenChecker.java | 64 ++++++------------- .../tests/when/ExhaustiveEnumIs.kt | 11 ++++ .../tests/when/ExhaustiveEnumIs.txt | 47 ++++++++++++++ .../tests/when/ExhaustiveEnumMixed.kt | 11 ++++ .../tests/when/ExhaustiveEnumMixed.txt | 47 ++++++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 12 ++++ 6 files changed, 147 insertions(+), 45 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.txt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.kt create mode 100644 compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java index 9b3949d7cd1..368938be928 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java @@ -92,16 +92,13 @@ public final class WhenChecker { ) { assert isEnumClass(enumClassDescriptor) : "isWhenOnEnumExhaustive should be called with an enum class descriptor"; - boolean notEmpty = false; + Set entryDescriptors = new HashSet(); for (DeclarationDescriptor descriptor : enumClassDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) { if (isEnumEntry(descriptor)) { - notEmpty = true; - if (!containsEnumEntryCase(expression, (ClassDescriptor) descriptor, trace)) { - return false; - } + entryDescriptors.add((ClassDescriptor) descriptor); } } - return notEmpty; + return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace); } private static void collectNestedSubclasses( @@ -187,25 +184,6 @@ public final class WhenChecker { return false; } - private static boolean containsEnumEntryCase( - @NotNull JetWhenExpression whenExpression, - @NotNull ClassDescriptor enumEntry, - @NotNull BindingTrace trace - ) { - assert enumEntry.getKind() == ClassKind.ENUM_ENTRY; - for (JetWhenEntry whenEntry : whenExpression.getEntries()) { - for (JetWhenCondition condition : whenEntry.getConditions()) { - if (!(condition instanceof JetWhenConditionWithExpression)) { - continue; - } - if (isCheckForEnumEntry((JetWhenConditionWithExpression) condition, enumEntry, trace)) { - return true; - } - } - } - return false; - } - private static boolean containsAllClassCases( @NotNull JetWhenExpression whenExpression, @NotNull Set memberDescriptors, @@ -215,27 +193,35 @@ public final class WhenChecker { for (JetWhenEntry whenEntry : whenExpression.getEntries()) { for (JetWhenCondition condition : whenEntry.getConditions()) { boolean negated = false; - JetType checkedType = null; + ClassDescriptor checkedDescriptor = null; if (condition instanceof JetWhenConditionIsPattern) { JetWhenConditionIsPattern conditionIsPattern = (JetWhenConditionIsPattern) condition; - checkedType = trace.get(BindingContext.TYPE, conditionIsPattern.getTypeReference()); + JetType checkedType = trace.get(BindingContext.TYPE, conditionIsPattern.getTypeReference()); + if (checkedType != null) { + checkedDescriptor = TypeUtils.getClassDescriptor(checkedType); + } negated = conditionIsPattern.isNegated(); } else if (condition instanceof JetWhenConditionWithExpression) { JetWhenConditionWithExpression conditionWithExpression = (JetWhenConditionWithExpression) condition; if (conditionWithExpression.getExpression() != null) { - checkedType = trace.getBindingContext().getType(conditionWithExpression.getExpression()); + JetSimpleNameExpression reference = getReference(conditionWithExpression.getExpression()); + if (reference != null) { + DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference); + if (target instanceof ClassDescriptor) { + checkedDescriptor = (ClassDescriptor) target; + } + } } } - if (checkedType == null) { - continue; - } - ClassDescriptor checkedDescriptor = TypeUtils.getClassDescriptor(checkedType); + // Checks are important only for nested subclasses of the sealed class // In additional, check without "is" is important only for objects if (checkedDescriptor == null || !memberDescriptors.contains(checkedDescriptor) - || (condition instanceof JetWhenConditionWithExpression && !DescriptorUtils.isObject(checkedDescriptor))) { + || (condition instanceof JetWhenConditionWithExpression + && !DescriptorUtils.isObject(checkedDescriptor) + && !DescriptorUtils.isEnumEntry(checkedDescriptor))) { continue; } if (negated) { @@ -268,18 +254,6 @@ public final class WhenChecker { return false; } - private static boolean isCheckForEnumEntry( - @NotNull JetWhenConditionWithExpression whenExpression, - @NotNull ClassDescriptor enumEntry, - @NotNull BindingTrace trace - ) { - JetSimpleNameExpression reference = getReference(whenExpression.getExpression()); - if (reference == null) return false; - - DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference); - return target == enumEntry; - } - @Nullable private static JetSimpleNameExpression getReference(@Nullable JetExpression expression) { if (expression == null) { diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt new file mode 100644 index 00000000000..3c58abd2073 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt @@ -0,0 +1,11 @@ +enum class MyEnum { + A, B, C +} + +fun foo(x: MyEnum): Int { + return when (x) { + is MyEnum.A -> 1 + is MyEnum.B -> 2 + is MyEnum.C -> 3 + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.txt new file mode 100644 index 00000000000..19bf157586f --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.txt @@ -0,0 +1,47 @@ +package + +internal fun foo(/*0*/ x: MyEnum): kotlin.Int + +internal final enum class MyEnum : kotlin.Enum { + public enum entry A : MyEnum { + private constructor A() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public enum entry B : MyEnum { + private constructor B() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public enum entry C : MyEnum { + private constructor C() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + private constructor MyEnum() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.kt new file mode 100644 index 00000000000..6ff1b9e4ba9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.kt @@ -0,0 +1,11 @@ +enum class MyEnum { + A, B, C +} + +fun foo(x: MyEnum): Int { + return when (x) { + MyEnum.A -> 1 + is MyEnum.B -> 2 + is MyEnum.C -> 3 + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.txt new file mode 100644 index 00000000000..19bf157586f --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.txt @@ -0,0 +1,47 @@ +package + +internal fun foo(/*0*/ x: MyEnum): kotlin.Int + +internal final enum class MyEnum : kotlin.Enum { + public enum entry A : MyEnum { + private constructor A() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public enum entry B : MyEnum { + private constructor B() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public enum entry C : MyEnum { + private constructor C() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + private constructor MyEnum() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 7d0ce4a3108..66e704d44ac 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -14040,6 +14040,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("ExhaustiveEnumIs.kt") + public void testExhaustiveEnumIs() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveEnumMixed.kt") + public void testExhaustiveEnumMixed() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveEnumMixed.kt"); + doTest(fileName); + } + @TestMetadata("ExhaustiveInitialization.kt") public void testExhaustiveInitialization() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt");