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 }