001    /*
002     * Copyright 2010-2013 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.jet.lang.cfg;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
022    import org.jetbrains.jet.lang.descriptors.ClassKind;
023    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
024    import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
025    import org.jetbrains.jet.lang.psi.*;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.BindingTrace;
028    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
029    import org.jetbrains.jet.lang.types.JetType;
030    import org.jetbrains.jet.lang.types.TypeProjection;
031    
032    import java.util.Collection;
033    import java.util.Collections;
034    
035    public final class WhenChecker {
036        private WhenChecker() {
037        }
038    
039        public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
040            JetExpression subjectExpression = expression.getSubjectExpression();
041            if (subjectExpression == null) return false;
042            JetType type = trace.get(BindingContext.EXPRESSION_TYPE, subjectExpression);
043            if (type == null) return false;
044            DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor();
045            if (!(declarationDescriptor instanceof ClassDescriptor)) return false;
046            ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
047            if (classDescriptor.getKind() != ClassKind.ENUM_CLASS || classDescriptor.getModality().isOverridable()) return false;
048            ClassDescriptor classObjectDescriptor = classDescriptor.getClassObjectDescriptor();
049            assert classObjectDescriptor != null : "Enum classes must have class object.";
050            JetScope memberScope = classObjectDescriptor.getMemberScope(Collections.<TypeProjection>emptyList());
051            Collection<ClassDescriptor> objectDescriptors = memberScope.getObjectDescriptors();
052            boolean isExhaust = true;
053            boolean notEmpty = false;
054            for (ClassDescriptor descriptor : objectDescriptors) {
055                if (descriptor.getKind() == ClassKind.ENUM_ENTRY) {
056                    notEmpty = true;
057                    if (!containsEnumEntryCase(expression, descriptor, trace)) {
058                        isExhaust = false;
059                    }
060                }
061            }
062            return isExhaust && notEmpty;
063        }
064    
065        private static boolean containsEnumEntryCase(
066                @NotNull JetWhenExpression whenExpression,
067                @NotNull ClassDescriptor enumEntry,
068                @NotNull BindingTrace trace
069        ) {
070            assert enumEntry.getKind() == ClassKind.ENUM_ENTRY;
071            for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
072                for (JetWhenCondition condition : whenEntry.getConditions()) {
073                    if (!(condition instanceof JetWhenConditionWithExpression)) {
074                        continue;
075                    }
076                    if (isCheckForEnumEntry((JetWhenConditionWithExpression) condition, enumEntry, trace)) {
077                        return true;
078                    }
079                }
080            }
081            return false;
082        }
083    
084        private static boolean isCheckForEnumEntry(
085                @NotNull JetWhenConditionWithExpression whenExpression,
086                @NotNull ClassDescriptor enumEntry,
087                @NotNull BindingTrace trace
088        ) {
089            JetSimpleNameExpression reference = getReference(whenExpression.getExpression());
090            if (reference == null) return false;
091    
092            DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference);
093            if (!(target instanceof VariableDescriptor)) {
094                return false;
095            }
096    
097            ClassDescriptor classDescriptor = trace.get(BindingContext.OBJECT_DECLARATION_CLASS, (VariableDescriptor) target);
098            return classDescriptor == enumEntry;
099        }
100    
101        @Nullable
102        private static JetSimpleNameExpression getReference(@Nullable JetExpression expression) {
103            if (expression == null) {
104                return null;
105            }
106            if (expression instanceof JetSimpleNameExpression) {
107                return (JetSimpleNameExpression) expression;
108            }
109            if (expression instanceof JetQualifiedExpression) {
110                return getReference(((JetQualifiedExpression) expression).getSelectorExpression());
111            }
112            return null;
113        }
114    }