001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.cfg;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
022 import org.jetbrains.kotlin.descriptors.*;
023 import org.jetbrains.kotlin.psi.*;
024 import org.jetbrains.kotlin.resolve.BindingContext;
025 import org.jetbrains.kotlin.resolve.BindingTrace;
026 import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils;
027 import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
028 import org.jetbrains.kotlin.types.JetType;
029 import org.jetbrains.kotlin.types.TypeUtils;
030
031 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
032 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
033 import static org.jetbrains.kotlin.types.TypesPackage.isFlexible;
034
035 public final class WhenChecker {
036 private WhenChecker() {
037 }
038
039 public static boolean mustHaveElse(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
040 JetType expectedType = trace.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression);
041 boolean isUnit = expectedType != null && KotlinBuiltIns.isUnit(expectedType);
042 // Some "statements" are actually expressions returned from lambdas, their expected types are non-null
043 boolean isStatement = BindingContextUtilPackage.isUsedAsStatement(expression, trace.getBindingContext()) && expectedType == null;
044
045 return !isUnit && !isStatement && !isWhenExhaustive(expression, trace);
046 }
047
048 public static boolean isWhenByEnum(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
049 return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null;
050 }
051
052 @Nullable
053 public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
054 if (type == null) return null;
055 ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
056 if (classDescriptor == null) return null;
057 if (classDescriptor.getKind() != ClassKind.ENUM_CLASS || classDescriptor.getModality().isOverridable()) return null;
058
059 return classDescriptor;
060 }
061
062 @Nullable
063 private static JetType whenSubjectType(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
064 JetExpression subjectExpression = expression.getSubjectExpression();
065 return subjectExpression == null ? null : context.getType(subjectExpression);
066 }
067
068 private static boolean isWhenOnBooleanExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
069 // It's assumed (and not checked) that expression is of the boolean type
070 boolean containsFalse = false;
071 boolean containsTrue = false;
072 for (JetWhenEntry whenEntry: expression.getEntries()) {
073 for (JetWhenCondition whenCondition : whenEntry.getConditions()) {
074 if (whenCondition instanceof JetWhenConditionWithExpression) {
075 JetExpression whenExpression = ((JetWhenConditionWithExpression) whenCondition).getExpression();
076 if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true;
077 if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true;
078 }
079 }
080 }
081 return containsFalse && containsTrue;
082 }
083
084 public static boolean isWhenOnEnumExhaustive(
085 @NotNull JetWhenExpression expression, @NotNull BindingTrace trace, @NotNull ClassDescriptor enumClassDescriptor) {
086 assert isEnumClass(enumClassDescriptor);
087 boolean notEmpty = false;
088 for (DeclarationDescriptor descriptor : enumClassDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) {
089 if (isEnumEntry(descriptor)) {
090 notEmpty = true;
091 if (!containsEnumEntryCase(expression, (ClassDescriptor) descriptor, trace)) {
092 return false;
093 }
094 }
095 }
096 return notEmpty;
097 }
098
099 public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
100 JetType type = whenSubjectType(expression, trace.getBindingContext());
101 if (type == null) return false;
102 ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type);
103
104 boolean exhaustive;
105 if (enumClassDescriptor == null) {
106 if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) {
107 exhaustive = isWhenOnBooleanExhaustive(expression, trace);
108 }
109 else {
110 // TODO: sealed hierarchies, etc.
111 exhaustive = false;
112 }
113 }
114 else {
115 exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor);
116 }
117 if (exhaustive) {
118 if (!TypeUtils.isNullableType(type)
119 || containsNullCase(expression, trace)
120 // Flexible (nullable) enum types are also counted as exhaustive
121 || (enumClassDescriptor != null && isFlexible(type))) {
122 trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
123 return true;
124 }
125 }
126 return false;
127 }
128
129 private static boolean containsEnumEntryCase(
130 @NotNull JetWhenExpression whenExpression,
131 @NotNull ClassDescriptor enumEntry,
132 @NotNull BindingTrace trace
133 ) {
134 assert enumEntry.getKind() == ClassKind.ENUM_ENTRY;
135 for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
136 for (JetWhenCondition condition : whenEntry.getConditions()) {
137 if (!(condition instanceof JetWhenConditionWithExpression)) {
138 continue;
139 }
140 if (isCheckForEnumEntry((JetWhenConditionWithExpression) condition, enumEntry, trace)) {
141 return true;
142 }
143 }
144 }
145 return false;
146 }
147
148 public static boolean containsNullCase(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
149 for (JetWhenEntry entry : expression.getEntries()) {
150 for (JetWhenCondition condition : entry.getConditions()) {
151 if (condition instanceof JetWhenConditionWithExpression) {
152 JetWhenConditionWithExpression conditionWithExpression = (JetWhenConditionWithExpression) condition;
153 if (conditionWithExpression.getExpression() != null) {
154 JetType type = trace.getBindingContext().getType(conditionWithExpression.getExpression());
155 if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) {
156 return true;
157 }
158 }
159 }
160 }
161 }
162 return false;
163 }
164
165 private static boolean isCheckForEnumEntry(
166 @NotNull JetWhenConditionWithExpression whenExpression,
167 @NotNull ClassDescriptor enumEntry,
168 @NotNull BindingTrace trace
169 ) {
170 JetSimpleNameExpression reference = getReference(whenExpression.getExpression());
171 if (reference == null) return false;
172
173 DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference);
174 return target == enumEntry;
175 }
176
177 @Nullable
178 private static JetSimpleNameExpression getReference(@Nullable JetExpression expression) {
179 if (expression == null) {
180 return null;
181 }
182 if (expression instanceof JetSimpleNameExpression) {
183 return (JetSimpleNameExpression) expression;
184 }
185 if (expression instanceof JetQualifiedExpression) {
186 return getReference(((JetQualifiedExpression) expression).getSelectorExpression());
187 }
188 return null;
189 }
190 }