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    }