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 }