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    }