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
017package org.jetbrains.jet.lang.cfg;
018
019import org.jetbrains.annotations.NotNull;
020import org.jetbrains.annotations.Nullable;
021import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
022import org.jetbrains.jet.lang.descriptors.ClassKind;
023import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
024import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
025import org.jetbrains.jet.lang.psi.*;
026import org.jetbrains.jet.lang.resolve.BindingContext;
027import org.jetbrains.jet.lang.resolve.BindingTrace;
028import org.jetbrains.jet.lang.resolve.scopes.JetScope;
029import org.jetbrains.jet.lang.types.JetType;
030import org.jetbrains.jet.lang.types.TypeProjection;
031
032import java.util.Collection;
033import java.util.Collections;
034
035public 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}