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.DescriptorUtils;
028 import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
029 import org.jetbrains.kotlin.types.JetType;
030 import org.jetbrains.kotlin.types.TypeUtils;
031
032 import java.util.HashSet;
033 import java.util.Set;
034
035 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
036 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
037 import static org.jetbrains.kotlin.types.TypesPackage.isFlexible;
038
039 public final class WhenChecker {
040 private WhenChecker() {
041 }
042
043 public static boolean mustHaveElse(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
044 JetType expectedType = trace.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression);
045 boolean isUnit = expectedType != null && KotlinBuiltIns.isUnit(expectedType);
046 // Some "statements" are actually expressions returned from lambdas, their expected types are non-null
047 boolean isStatement = BindingContextUtilPackage.isUsedAsStatement(expression, trace.getBindingContext()) && expectedType == null;
048
049 return !isUnit && !isStatement && !isWhenExhaustive(expression, trace);
050 }
051
052 public static boolean isWhenByEnum(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
053 return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null;
054 }
055
056 @Nullable
057 public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
058 if (type == null) return null;
059 ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
060 if (classDescriptor == null) return null;
061 if (classDescriptor.getKind() != ClassKind.ENUM_CLASS || classDescriptor.getModality().isOverridable()) return null;
062
063 return classDescriptor;
064 }
065
066 @Nullable
067 private static JetType whenSubjectType(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
068 JetExpression subjectExpression = expression.getSubjectExpression();
069 return subjectExpression == null ? null : context.getType(subjectExpression);
070 }
071
072 private static boolean isWhenOnBooleanExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
073 // It's assumed (and not checked) that expression is of the boolean type
074 boolean containsFalse = false;
075 boolean containsTrue = false;
076 for (JetWhenEntry whenEntry: expression.getEntries()) {
077 for (JetWhenCondition whenCondition : whenEntry.getConditions()) {
078 if (whenCondition instanceof JetWhenConditionWithExpression) {
079 JetExpression whenExpression = ((JetWhenConditionWithExpression) whenCondition).getExpression();
080 if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true;
081 if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true;
082 }
083 }
084 }
085 return containsFalse && containsTrue;
086 }
087
088 public static boolean isWhenOnEnumExhaustive(
089 @NotNull JetWhenExpression expression,
090 @NotNull BindingTrace trace,
091 @NotNull ClassDescriptor enumClassDescriptor
092 ) {
093 assert isEnumClass(enumClassDescriptor) :
094 "isWhenOnEnumExhaustive should be called with an enum class descriptor";
095 Set<ClassDescriptor> entryDescriptors = new HashSet<ClassDescriptor>();
096 for (DeclarationDescriptor descriptor : enumClassDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) {
097 if (isEnumEntry(descriptor)) {
098 entryDescriptors.add((ClassDescriptor) descriptor);
099 }
100 }
101 return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace);
102 }
103
104 private static void collectNestedSubclasses(
105 @NotNull ClassDescriptor baseDescriptor,
106 @NotNull ClassDescriptor currentDescriptor,
107 @NotNull Set<ClassDescriptor> subclasses
108 ) {
109 for (DeclarationDescriptor descriptor : currentDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) {
110 if (descriptor instanceof ClassDescriptor) {
111 ClassDescriptor memberClassDescriptor = (ClassDescriptor) descriptor;
112 if (DescriptorUtils.isDirectSubclass(memberClassDescriptor, baseDescriptor)) {
113 subclasses.add(memberClassDescriptor);
114 }
115 collectNestedSubclasses(baseDescriptor, memberClassDescriptor, subclasses);
116 }
117 }
118 }
119
120 private static boolean isWhenOnSealedClassExhaustive(
121 @NotNull JetWhenExpression expression,
122 @NotNull BindingTrace trace,
123 @NotNull ClassDescriptor classDescriptor
124 ) {
125 assert classDescriptor.getModality() == Modality.SEALED :
126 "isWhenOnSealedClassExhaustive should be called with a sealed class descriptor";
127 Set<ClassDescriptor> memberClassDescriptors = new HashSet<ClassDescriptor>();
128 collectNestedSubclasses(classDescriptor, classDescriptor, memberClassDescriptors);
129 // When on a sealed class without derived members is considered non-exhaustive (see test WhenOnEmptySealed)
130 return !memberClassDescriptors.isEmpty() && containsAllClassCases(expression, memberClassDescriptors, trace);
131 }
132
133 /**
134 * It's assumed that function is called for a final type. In this case the only possible smart cast is to not nullable type.
135 * @return true if type is nullable, and cannot be smart casted
136 */
137 private static boolean isNullableTypeWithoutPossibleSmartCast(
138 @Nullable JetExpression expression,
139 @NotNull JetType type,
140 @NotNull BindingContext context
141 ) {
142 if (expression == null) return false; // Normally should not happen
143 if (!TypeUtils.isNullableType(type)) return false;
144 // We cannot read data flow information here due to lack of inputs (module descriptor is necessary)
145 if (context.get(BindingContext.SMARTCAST, expression) != null) {
146 // We have smart cast from enum or boolean to something
147 // Not very nice but we *can* decide it was smart cast to not-null
148 // because both enum and boolean are final
149 return false;
150 }
151 return true;
152 }
153
154 public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
155 JetType type = whenSubjectType(expression, trace.getBindingContext());
156 if (type == null) return false;
157 ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type);
158
159 boolean exhaustive;
160 if (enumClassDescriptor == null) {
161 if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) {
162 exhaustive = isWhenOnBooleanExhaustive(expression, trace);
163 }
164 else {
165 ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
166 exhaustive = (classDescriptor != null
167 && classDescriptor.getModality() == Modality.SEALED
168 && isWhenOnSealedClassExhaustive(expression, trace, classDescriptor));
169 }
170 }
171 else {
172 exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor);
173 }
174 if (exhaustive) {
175 if (// Flexible (nullable) enum types are also counted as exhaustive
176 (enumClassDescriptor != null && isFlexible(type))
177 || containsNullCase(expression, trace)
178 || !isNullableTypeWithoutPossibleSmartCast(expression.getSubjectExpression(), type, trace.getBindingContext())) {
179
180 trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
181 return true;
182 }
183 }
184 return false;
185 }
186
187 private static boolean containsAllClassCases(
188 @NotNull JetWhenExpression whenExpression,
189 @NotNull Set<ClassDescriptor> memberDescriptors,
190 @NotNull BindingTrace trace
191 ) {
192 Set<ClassDescriptor> checkedDescriptors = new HashSet<ClassDescriptor>();
193 for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
194 for (JetWhenCondition condition : whenEntry.getConditions()) {
195 boolean negated = false;
196 ClassDescriptor checkedDescriptor = null;
197 if (condition instanceof JetWhenConditionIsPattern) {
198 JetWhenConditionIsPattern conditionIsPattern = (JetWhenConditionIsPattern) condition;
199 JetType checkedType = trace.get(BindingContext.TYPE, conditionIsPattern.getTypeReference());
200 if (checkedType != null) {
201 checkedDescriptor = TypeUtils.getClassDescriptor(checkedType);
202 }
203 negated = conditionIsPattern.isNegated();
204 }
205 else if (condition instanceof JetWhenConditionWithExpression) {
206 JetWhenConditionWithExpression conditionWithExpression = (JetWhenConditionWithExpression) condition;
207 if (conditionWithExpression.getExpression() != null) {
208 JetSimpleNameExpression reference = getReference(conditionWithExpression.getExpression());
209 if (reference != null) {
210 DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference);
211 if (target instanceof ClassDescriptor) {
212 checkedDescriptor = (ClassDescriptor) target;
213 }
214 }
215 }
216 }
217
218 // Checks are important only for nested subclasses of the sealed class
219 // In additional, check without "is" is important only for objects
220 if (checkedDescriptor == null
221 || !memberDescriptors.contains(checkedDescriptor)
222 || (condition instanceof JetWhenConditionWithExpression
223 && !DescriptorUtils.isObject(checkedDescriptor)
224 && !DescriptorUtils.isEnumEntry(checkedDescriptor))) {
225 continue;
226 }
227 if (negated) {
228 if (checkedDescriptors.contains(checkedDescriptor)) return true; // all members are already there
229 checkedDescriptors.addAll(memberDescriptors);
230 checkedDescriptors.remove(checkedDescriptor);
231 }
232 else {
233 checkedDescriptors.add(checkedDescriptor);
234 }
235 }
236 }
237 return checkedDescriptors.containsAll(memberDescriptors);
238 }
239
240 public static boolean containsNullCase(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
241 for (JetWhenEntry entry : expression.getEntries()) {
242 for (JetWhenCondition condition : entry.getConditions()) {
243 if (condition instanceof JetWhenConditionWithExpression) {
244 JetWhenConditionWithExpression conditionWithExpression = (JetWhenConditionWithExpression) condition;
245 if (conditionWithExpression.getExpression() != null) {
246 JetType type = trace.getBindingContext().getType(conditionWithExpression.getExpression());
247 if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) {
248 return true;
249 }
250 }
251 }
252 }
253 }
254 return false;
255 }
256
257 @Nullable
258 private static JetSimpleNameExpression getReference(@Nullable JetExpression expression) {
259 if (expression == null) {
260 return null;
261 }
262 if (expression instanceof JetSimpleNameExpression) {
263 return (JetSimpleNameExpression) expression;
264 }
265 if (expression instanceof JetQualifiedExpression) {
266 return getReference(((JetQualifiedExpression) expression).getSelectorExpression());
267 }
268 return null;
269 }
270 }