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