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 }