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 }