Reorganization of when checking: exhaustion predicates clarified, analysis on platform enum warnings was moved to frontend.java

This commit is contained in:
Mikhail Glukhikh
2015-05-25 12:37:04 +03:00
parent e4c242e66b
commit f4977a1108
8 changed files with 46 additions and 45 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.load.kotlin
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
@@ -48,6 +49,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker
import org.jetbrains.kotlin.types.flexibility
import org.jetbrains.kotlin.types.isFlexible
public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
@@ -221,6 +223,20 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
}
when (expression) {
is JetWhenExpression ->
if (expression.getElseExpression() == null) {
// Check for conditionally-exhaustive when on platform enums, see KT-6399
val type = expression.getSubjectExpression()?.let { c.trace.getType(it) } ?: return
if (type.isFlexible() && TypeUtils.isNullableType(type.flexibility().upperBound) && !type.getAnnotations().isMarkedNotNull()) {
val enumClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfEnum(type) ?: return
if (WhenChecker.isWhenOnEnumExhaustive(expression, c.trace, enumClassDescriptor)
&& !WhenChecker.containsNullCase(expression, c.trace)) {
c.trace.report(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA.on(expression.getSubjectExpression()))
}
}
}
is JetPostfixExpression ->
if (expression.getOperationToken() == JetTokens.EXCLEXCL) {
val baseExpression = expression.getBaseExpression() ?: return
@@ -305,4 +321,4 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
}
}
}
}
}
@@ -66,6 +66,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
"Expected type does not accept nulls in {0}, but the value may be null in {1}", Renderers.TO_STRING, Renderers.TO_STRING);
MAP.put(ErrorsJvm.TRAIT_CANT_CALL_DEFAULT_METHOD_VIA_SUPER, "Interfaces can't call Java default methods via super");
MAP.put(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA, "Enum argument ''{0}'' can be null in Java, but exhaustive when contains no null branch");
}
@@ -79,6 +79,8 @@ public interface ErrorsJvm {
DiagnosticFactory2<JetElement, NullabilityInformationSource, NullabilityInformationSource> NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS = DiagnosticFactory2.create(WARNING);
DiagnosticFactory0<JetExpression> WHEN_ENUM_CAN_BE_NULL_IN_JAVA = DiagnosticFactory0.create(WARNING);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -16,12 +16,10 @@
package org.jetbrains.kotlin.cfg;
import com.intellij.codeInsight.AnnotationUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
@@ -32,6 +30,7 @@ import org.jetbrains.kotlin.types.TypeUtils;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
import static org.jetbrains.kotlin.types.TypesPackage.isFlexible;
public final class WhenChecker {
private WhenChecker() {
@@ -46,29 +45,12 @@ public final class WhenChecker {
return !isUnit && !isStatement && !isWhenExhaustive(expression, trace);
}
private static final FqName notNullAnnotationName = new FqName(AnnotationUtil.NOT_NULL);
public static boolean isExhaustiveWhenOnPlatformNullableEnum(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
JetType type = whenSubjectType(expression, trace.getBindingContext());
if (type == null) return false;
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
return (isPlatformEnum(type, classDescriptor)
&& isWhenOnEnumExhaustive(expression, trace, classDescriptor)
// nullable from Kotlin side
&& TypeUtils.isNullableType(type)
// and from Java side too
&& type.getAnnotations().findAnnotation(notNullAnnotationName) == null
&& type.getAnnotations().findExternalAnnotation(notNullAnnotationName) == null
// but no null case
&& !containsNullCase(expression, trace));
}
public static boolean isWhenByEnum(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null;
}
@Nullable
private static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
if (type == null) return null;
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
if (classDescriptor == null) return null;
@@ -99,7 +81,7 @@ public final class WhenChecker {
return containsFalse && containsTrue;
}
private static boolean isWhenOnEnumExhaustive(
public static boolean isWhenOnEnumExhaustive(
@NotNull JetWhenExpression expression, @NotNull BindingTrace trace, @NotNull ClassDescriptor enumClassDescriptor) {
assert isEnumClass(enumClassDescriptor);
boolean notEmpty = false;
@@ -114,19 +96,13 @@ public final class WhenChecker {
return notEmpty;
}
private static boolean isPlatformEnum(@NotNull JetType type, @Nullable ClassDescriptor classDescriptor) {
// instanceof JetClass are Kotlin types, as well as nullable types
return classDescriptor != null && classDescriptor.getKind() == ClassKind.ENUM_CLASS
&& !(type instanceof JetClass) && !type.isMarkedNullable();
}
public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
JetType type = whenSubjectType(expression, trace.getBindingContext());
if (type == null) return false;
ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type);
ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type);
boolean exhaustive;
if (classDescriptor == null) {
if (enumClassDescriptor == null) {
if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) {
exhaustive = isWhenOnBooleanExhaustive(expression, trace);
}
@@ -136,11 +112,16 @@ public final class WhenChecker {
}
}
else {
exhaustive = isWhenOnEnumExhaustive(expression, trace, classDescriptor);
exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor);
}
if (exhaustive && (!TypeUtils.isNullableType(type) || containsNullCase(expression, trace) || isPlatformEnum(type, classDescriptor))) {
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
return true;
if (exhaustive) {
if (!TypeUtils.isNullableType(type)
|| containsNullCase(expression, trace)
// Flexible (nullable) enum types are also counted as exhaustive
|| (enumClassDescriptor != null && isFlexible(type))) {
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
return true;
}
}
return false;
}
@@ -164,7 +145,7 @@ public final class WhenChecker {
return false;
}
private static boolean containsNullCase(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
public static boolean containsNullCase(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
for (JetWhenEntry entry : expression.getEntries()) {
for (JetWhenCondition condition : entry.getConditions()) {
if (condition instanceof JetWhenConditionWithExpression) {
@@ -618,7 +618,6 @@ public interface Errors {
DiagnosticFactory0<JetWhenCondition> EXPECTED_CONDITION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetWhenEntry> ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY);
DiagnosticFactory0<JetWhenExpression> NO_ELSE_IN_WHEN = DiagnosticFactory0.create(ERROR, WHEN_EXPRESSION);
DiagnosticFactory0<JetExpression> WHEN_ENUM_CAN_BE_NULL_IN_JAVA = DiagnosticFactory0.create(WARNING);
// Type mismatch
@@ -385,7 +385,6 @@ public class DefaultErrorMessages {
MAP.put(ELSE_MISPLACED_IN_WHEN, "'else' entry must be the last one in a when-expression");
MAP.put(NO_ELSE_IN_WHEN, "'when' expression must contain 'else' branch");
MAP.put(WHEN_ENUM_CAN_BE_NULL_IN_JAVA, "Enum argument ''{0}'' can be null in Java, but exhaustive when contains no null branch");
MAP.put(TYPE_MISMATCH_IN_RANGE, "Type mismatch: incompatible types of range and element checked in it");
MAP.put(CYCLIC_INHERITANCE_HIERARCHY, "There's a cycle in the inheritance hierarchy for this type");
@@ -186,6 +186,11 @@ public class DataFlowUtils {
return expressionType;
}
if (expression instanceof JetWhenExpression) {
// No need in additional check because type mismatch is already reported for entries
return expressionType;
}
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c);
for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) {
@@ -130,15 +130,12 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
if (commonDataFlowInfo == null) {
commonDataFlowInfo = context.dataFlowInfo;
}
// Check for conditionally-exhaustive platform enums, see KT-6399
if (expression.getElseExpression() == null
&& WhenChecker.isExhaustiveWhenOnPlatformNullableEnum(expression, context.trace)) {
context.trace.report(WHEN_ENUM_CAN_BE_NULL_IN_JAVA.on(expression.getSubjectExpression()));
}
return TypeInfoFactoryPackage.createTypeInfo(expressionTypes.isEmpty() ? null : DataFlowUtils.checkImplicitCast(
CommonSupertypes.commonSupertype(expressionTypes), expression,
contextWithExpectedType, isStatement),
return TypeInfoFactoryPackage.createTypeInfo(expressionTypes.isEmpty() ? null : DataFlowUtils.checkType(
DataFlowUtils.checkImplicitCast(
CommonSupertypes.commonSupertype(expressionTypes), expression,
contextWithExpectedType, isStatement),
expression, contextWithExpectedType),
commonDataFlowInfo,
loopBreakContinuePossible,
contextWithExpectedType.dataFlowInfo);