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