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.ClassDescriptor;
023    import org.jetbrains.kotlin.descriptors.ClassKind;
024    import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
025    import org.jetbrains.kotlin.psi.*;
026    import org.jetbrains.kotlin.resolve.BindingContext;
027    import org.jetbrains.kotlin.resolve.BindingTrace;
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 static org.jetbrains.kotlin.resolve.BindingContext.EXPRESSION_TYPE;
033    import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
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        private static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
054            if (type == null) return null;
055            DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor();
056            if (!(declarationDescriptor instanceof ClassDescriptor)) return null;
057            ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
058            if (classDescriptor.getKind() != ClassKind.ENUM_CLASS || classDescriptor.getModality().isOverridable()) return null;
059    
060            return classDescriptor;
061        }
062    
063        @Nullable
064        private static JetType whenSubjectType(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
065            JetExpression subjectExpression = expression.getSubjectExpression();
066            return subjectExpression == null ? null : context.get(EXPRESSION_TYPE, subjectExpression);
067        }
068    
069        private static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
070            JetType type = whenSubjectType(expression, trace.getBindingContext());
071            ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type);
072    
073            if (type == null || classDescriptor == null) return false;
074    
075            boolean isExhaust = true;
076            boolean notEmpty = false;
077            for (DeclarationDescriptor descriptor : classDescriptor.getUnsubstitutedInnerClassesScope().getAllDescriptors()) {
078                if (isEnumEntry(descriptor)) {
079                    notEmpty = true;
080                    if (!containsEnumEntryCase(expression, (ClassDescriptor) descriptor, trace)) {
081                        isExhaust = false;
082                    }
083                }
084            }
085            boolean exhaustive = isExhaust && notEmpty && (!TypeUtils.isNullableType(type) || containsNullCase(expression, trace));
086            if (exhaustive) {
087                trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
088            }
089            return exhaustive;
090        }
091    
092        private static boolean containsEnumEntryCase(
093                @NotNull JetWhenExpression whenExpression,
094                @NotNull ClassDescriptor enumEntry,
095                @NotNull BindingTrace trace
096        ) {
097            assert enumEntry.getKind() == ClassKind.ENUM_ENTRY;
098            for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
099                for (JetWhenCondition condition : whenEntry.getConditions()) {
100                    if (!(condition instanceof JetWhenConditionWithExpression)) {
101                        continue;
102                    }
103                    if (isCheckForEnumEntry((JetWhenConditionWithExpression) condition, enumEntry, trace)) {
104                        return true;
105                    }
106                }
107            }
108            return false;
109        }
110    
111        private static boolean containsNullCase(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
112            for (JetWhenEntry entry : expression.getEntries()) {
113                for (JetWhenCondition condition : entry.getConditions()) {
114                    if (condition instanceof JetWhenConditionWithExpression) {
115                        JetType type = trace.getBindingContext().get(
116                                EXPRESSION_TYPE, ((JetWhenConditionWithExpression) condition).getExpression()
117                        );
118                        if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) {
119                            return true;
120                        }
121                    }
122                }
123            }
124            return false;
125        }
126    
127        private static boolean isCheckForEnumEntry(
128                @NotNull JetWhenConditionWithExpression whenExpression,
129                @NotNull ClassDescriptor enumEntry,
130                @NotNull BindingTrace trace
131        ) {
132            JetSimpleNameExpression reference = getReference(whenExpression.getExpression());
133            if (reference == null) return false;
134    
135            DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference);
136            return target == enumEntry;
137        }
138    
139        @Nullable
140        private static JetSimpleNameExpression getReference(@Nullable JetExpression expression) {
141            if (expression == null) {
142                return null;
143            }
144            if (expression instanceof JetSimpleNameExpression) {
145                return (JetSimpleNameExpression) expression;
146            }
147            if (expression instanceof JetQualifiedExpression) {
148                return getReference(((JetQualifiedExpression) expression).getSelectorExpression());
149            }
150            return null;
151        }
152    }