From 1d71e820bb28203dbda68693b1ca08fe33ddda89 Mon Sep 17 00:00:00 2001 From: svtk Date: Mon, 30 Jan 2012 13:39:44 +0400 Subject: [PATCH] KT-1185 Support full enumeration check for 'when' --- .../resolve/java/JavaClassMembersScope.java | 11 ++-- .../jet/lang/cfg/JetControlFlowProcessor.java | 5 +- .../jetbrains/jet/lang/cfg/WhenChecker.java | 66 +++++++++++++++++++ .../lang/resolve/AbstractScopeAdapter.java | 6 ++ .../jet/lang/resolve/scopes/ChainedScope.java | 15 +++-- .../jet/lang/resolve/scopes/JetScope.java | 3 + .../jet/lang/resolve/scopes/JetScopeImpl.java | 11 ++-- .../resolve/scopes/SubstitutingScope.java | 6 ++ .../resolve/scopes/WritableScopeImpl.java | 12 +++- .../resolve/scopes/WriteThroughScope.java | 12 ++++ .../jetbrains/jet/lang/types/ErrorUtils.java | 8 ++- .../tests/controlFlowAnalysis/kt1185enums.jet | 57 ++++++++++++++++ 12 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.jet diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaClassMembersScope.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaClassMembersScope.java index 3eeb90983e5..264564f8811 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaClassMembersScope.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaClassMembersScope.java @@ -10,11 +10,8 @@ import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; -import org.jetbrains.jet.lang.types.TypeSubstitutor; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author abreslav @@ -66,6 +63,12 @@ public class JavaClassMembersScope extends JavaClassOrPackageScope { return null; } + @NotNull + @Override + public Set getObjectDescriptors() { + return Collections.emptySet(); + } + @NotNull @Override public Collection getAllDescriptors() { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java index c1ba50a53c1..a1566351f28 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java @@ -762,8 +762,6 @@ public class JetControlFlowProcessor { @Override public void visitWhenExpression(JetWhenExpression expression) { - // TODO : no more than one else - // TODO : else must be the last JetExpression subjectExpression = expression.getSubjectExpression(); if (subjectExpression != null) { value(subjectExpression, inCondition); @@ -815,7 +813,8 @@ public class JetControlFlowProcessor { } } builder.bindLabel(doneLabel); - if (!hasElseOrIrrefutableBranch) { + boolean isWhenExhaust = WhenChecker.isWhenExhaust(expression, trace); + if (!hasElseOrIrrefutableBranch && !isWhenExhaust) { trace.report(NO_ELSE_IN_WHEN.on(expression)); } builder.stopAllowDead(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java new file mode 100644 index 00000000000..fca1bd2616e --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java @@ -0,0 +1,66 @@ +package org.jetbrains.jet.lang.cfg; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.descriptors.ClassKind; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.Modality; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingTrace; +import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.TypeProjection; + +import java.util.Collections; +import java.util.Set; + +/** + * @author svtk + */ +public class WhenChecker { + public static boolean isWhenExhaust(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { + JetExpression subjectExpression = expression.getSubjectExpression(); + if (subjectExpression == null) return false; + JetType type = trace.get(BindingContext.EXPRESSION_TYPE, subjectExpression); + if (type == null) return false; + DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor(); + if (!(declarationDescriptor instanceof ClassDescriptor)) return false; + ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; + if (classDescriptor.getKind() != ClassKind.ENUM_CLASS || classDescriptor.getModality().isOverridable()) return false; + ClassDescriptor classObjectDescriptor = classDescriptor.getClassObjectDescriptor(); + assert classObjectDescriptor != null; + JetScope memberScope = classObjectDescriptor.getMemberScope(Collections.emptyList()); + Set objectDescriptors = memberScope.getObjectDescriptors(); + boolean isExhaust = true; + boolean notEmpty = false; + for (ClassDescriptor descriptor : objectDescriptors) { + if (descriptor.getKind() == ClassKind.ENUM_ENTRY) { + notEmpty = true; + if (!containsEnumEntryCase(expression, descriptor, trace)) { + isExhaust = false; + } + } + } + return isExhaust && notEmpty; + } + + 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) { + JetExpressionPattern pattern = ((JetWhenConditionWithExpression) condition).getPattern(); + if (pattern == null) continue; + JetExpression patternExpression = pattern.getExpression(); + JetType type = trace.get(BindingContext.EXPRESSION_TYPE, patternExpression); + if (type == null) continue; + if (type.getConstructor().getDeclarationDescriptor() == enumEntry) { + return true; + } + } + } + } + return false; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java index de263c901a1..bcad6f1aa25 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java @@ -50,6 +50,12 @@ public abstract class AbstractScopeAdapter implements JetScope { return getWorkerScope().getObjectDescriptor(name); } + @NotNull + @Override + public Set getObjectDescriptors() { + return getWorkerScope().getObjectDescriptors(); + } + @NotNull @Override public Set getProperties(@NotNull String name) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/ChainedScope.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/ChainedScope.java index 12239be35d9..057fb49f0a0 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/ChainedScope.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/ChainedScope.java @@ -5,10 +5,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @author abreslav @@ -41,6 +38,16 @@ public class ChainedScope implements JetScope { return null; } + @NotNull + @Override + public Set getObjectDescriptors() { + Set objectDescriptors = Sets.newHashSet(); + for (JetScope scope : scopeChain) { + objectDescriptors.addAll(scope.getObjectDescriptors()); + } + return objectDescriptors; + } + @Override public NamespaceDescriptor getNamespace(@NotNull String name) { for (JetScope jetScope : scopeChain) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java index f4e7e5f5b20..53e1c23176b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java @@ -32,6 +32,9 @@ public interface JetScope { @Nullable ClassDescriptor getObjectDescriptor(@NotNull String name); + @NotNull + Set getObjectDescriptors(); + @Nullable NamespaceDescriptor getNamespace(@NotNull String name); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeImpl.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeImpl.java index e92cb3d81fd..5f325cd3809 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeImpl.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeImpl.java @@ -4,10 +4,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @author abreslav @@ -23,6 +20,12 @@ public abstract class JetScopeImpl implements JetScope { return null; } + @NotNull + @Override + public Set getObjectDescriptors() { + return Collections.emptySet(); + } + @NotNull @Override public Set getProperties(@NotNull String name) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/SubstitutingScope.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/SubstitutingScope.java index e0d64d3fdd2..e9f86677e02 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/SubstitutingScope.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/SubstitutingScope.java @@ -81,6 +81,12 @@ public class SubstitutingScope implements JetScope { return substitute(workerScope.getObjectDescriptor(name)); } + @NotNull + @Override + public Set getObjectDescriptors() { + return substitute(workerScope.getObjectDescriptors()); + } + @NotNull @Override public Set getFunctions(@NotNull String name) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WritableScopeImpl.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WritableScopeImpl.java index 119de952eb6..fb627458202 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WritableScopeImpl.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WritableScopeImpl.java @@ -130,7 +130,7 @@ public class WritableScopeImpl extends WritableScopeWithImports { } @NotNull - public Map getObjectDescriptors() { + private Map getObjectDescriptorsMap() { if (objectDescriptors == null) { objectDescriptors = Maps.newHashMap(); } @@ -299,7 +299,7 @@ public class WritableScopeImpl extends WritableScopeWithImports { public void addObjectDescriptor(@NotNull ClassDescriptor objectDescriptor) { checkMayWrite(); - getObjectDescriptors().put(objectDescriptor.getName(), objectDescriptor); + getObjectDescriptorsMap().put(objectDescriptor.getName(), objectDescriptor); } @Override @@ -373,7 +373,13 @@ public class WritableScopeImpl extends WritableScopeWithImports { @Override public ClassDescriptor getObjectDescriptor(@NotNull String name) { - return getObjectDescriptors().get(name); + return getObjectDescriptorsMap().get(name); + } + + @NotNull + @Override + public Set getObjectDescriptors() { + return Sets.newHashSet(getObjectDescriptorsMap().values()); } @Override diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WriteThroughScope.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WriteThroughScope.java index fe4a9f137da..5eb0c4cda49 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WriteThroughScope.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/WriteThroughScope.java @@ -137,6 +137,18 @@ public class WriteThroughScope extends WritableScopeWithImports { return super.getObjectDescriptor(name); // Imports } + @NotNull + @Override + public Set getObjectDescriptors() { + checkMayRead(); + Set objectDescriptors = Sets.newHashSet(); + + objectDescriptors.addAll(super.getObjectDescriptors()); + objectDescriptors.addAll(getWorkerScope().getObjectDescriptors()); + objectDescriptors.addAll(writableWorker.getObjectDescriptors()); + return objectDescriptors; + } + @Override public void addLabeledDeclaration(@NotNull DeclarationDescriptor descriptor) { checkMayWrite(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/ErrorUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/ErrorUtils.java index a170c4ac6ed..ae8f5cdd0d3 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/ErrorUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/ErrorUtils.java @@ -4,8 +4,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; -import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import java.util.*; @@ -33,6 +33,12 @@ public class ErrorUtils { return ERROR_CLASS; } + @NotNull + @Override + public Set getObjectDescriptors() { + return Collections.emptySet(); + } + @NotNull @Override public Set getProperties(@NotNull String name) { diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.jet b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.jet new file mode 100644 index 00000000000..8845ac246d7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt1185enums.jet @@ -0,0 +1,57 @@ +//KT-1185 Support full enumeration check for 'when' + +package kt1185 + +enum class Direction { + NORTH + SOUTH + WEST + EAST +} + +enum class Color(val rgb : Int) { + RED : Color(0xFF0000) + GREEN : Color(0x00FF00) + BLUE : Color(0x0000FF) +} + +fun foo(d: Direction) = when(d) { //no 'else' should be requested + Direction.NORTH -> 1 + Direction.SOUTH -> 2 + Direction.WEST -> 3 + Direction.EAST -> 4 +} + +fun foo1(d: Direction) = when(d) { + Direction.NORTH -> 1 + Direction.SOUTH -> 2 + Direction.WEST -> 3 +} + +fun bar(c: Color) = when (c) { + Color.RED -> 1 + Color.GREEN -> 2 + Color.BLUE -> 3 +} + +fun bar1(c: Color) = when (c) { + Color.RED -> 1 + Color.GREEN -> 2 +} + +open enum class SomeEnum() {} + +enum class MyEnum : SomeEnum() { + A + B +} + +fun g(me: SomeEnum) = when (me) { + MyEnum.A -> 1 + MyEnum.B -> 2 +} + +fun t2(me: MyEnum) = when (me) { + MyEnum.A -> 1 + MyEnum.B -> 2 +}