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