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 }