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 package org.jetbrains.kotlin.load.kotlin
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors 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.JetType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker
import org.jetbrains.kotlin.types.flexibility
import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.types.isFlexible
public object KotlinJvmCheckerProvider : AdditionalCheckerProvider( public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
@@ -221,6 +223,20 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
} }
when (expression) { 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 -> is JetPostfixExpression ->
if (expression.getOperationToken() == JetTokens.EXCLEXCL) { if (expression.getOperationToken() == JetTokens.EXCLEXCL) {
val baseExpression = expression.getBaseExpression() ?: return 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); "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.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); 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") @SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() { Object _initializer = new Object() {
{ {
@@ -16,12 +16,10 @@
package org.jetbrains.kotlin.cfg; package org.jetbrains.kotlin.cfg;
import com.intellij.codeInsight.AnnotationUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace; 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.isEnumEntry;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass; import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
import static org.jetbrains.kotlin.types.TypesPackage.isFlexible;
public final class WhenChecker { public final class WhenChecker {
private WhenChecker() { private WhenChecker() {
@@ -46,29 +45,12 @@ public final class WhenChecker {
return !isUnit && !isStatement && !isWhenExhaustive(expression, trace); 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) { public static boolean isWhenByEnum(@NotNull JetWhenExpression expression, @NotNull BindingContext context) {
return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null; return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null;
} }
@Nullable @Nullable
private static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) { public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable JetType type) {
if (type == null) return null; if (type == null) return null;
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
if (classDescriptor == null) return null; if (classDescriptor == null) return null;
@@ -99,7 +81,7 @@ public final class WhenChecker {
return containsFalse && containsTrue; return containsFalse && containsTrue;
} }
private static boolean isWhenOnEnumExhaustive( public static boolean isWhenOnEnumExhaustive(
@NotNull JetWhenExpression expression, @NotNull BindingTrace trace, @NotNull ClassDescriptor enumClassDescriptor) { @NotNull JetWhenExpression expression, @NotNull BindingTrace trace, @NotNull ClassDescriptor enumClassDescriptor) {
assert isEnumClass(enumClassDescriptor); assert isEnumClass(enumClassDescriptor);
boolean notEmpty = false; boolean notEmpty = false;
@@ -114,19 +96,13 @@ public final class WhenChecker {
return notEmpty; 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) { public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
JetType type = whenSubjectType(expression, trace.getBindingContext()); JetType type = whenSubjectType(expression, trace.getBindingContext());
if (type == null) return false; if (type == null) return false;
ClassDescriptor classDescriptor = getClassDescriptorOfTypeIfEnum(type); ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type);
boolean exhaustive; boolean exhaustive;
if (classDescriptor == null) { if (enumClassDescriptor == null) {
if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) { if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) {
exhaustive = isWhenOnBooleanExhaustive(expression, trace); exhaustive = isWhenOnBooleanExhaustive(expression, trace);
} }
@@ -136,11 +112,16 @@ public final class WhenChecker {
} }
} }
else { else {
exhaustive = isWhenOnEnumExhaustive(expression, trace, classDescriptor); exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor);
} }
if (exhaustive && (!TypeUtils.isNullableType(type) || containsNullCase(expression, trace) || isPlatformEnum(type, classDescriptor))) { if (exhaustive) {
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression); if (!TypeUtils.isNullableType(type)
return true; || 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; return false;
} }
@@ -164,7 +145,7 @@ public final class WhenChecker {
return false; 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 (JetWhenEntry entry : expression.getEntries()) {
for (JetWhenCondition condition : entry.getConditions()) { for (JetWhenCondition condition : entry.getConditions()) {
if (condition instanceof JetWhenConditionWithExpression) { if (condition instanceof JetWhenConditionWithExpression) {
@@ -618,7 +618,6 @@ public interface Errors {
DiagnosticFactory0<JetWhenCondition> EXPECTED_CONDITION = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<JetWhenCondition> EXPECTED_CONDITION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetWhenEntry> ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY); DiagnosticFactory0<JetWhenEntry> ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY);
DiagnosticFactory0<JetWhenExpression> NO_ELSE_IN_WHEN = DiagnosticFactory0.create(ERROR, WHEN_EXPRESSION); 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 // 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(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(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(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"); 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; 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); DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c);
for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) { for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) {
@@ -130,15 +130,12 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
if (commonDataFlowInfo == null) { if (commonDataFlowInfo == null) {
commonDataFlowInfo = context.dataFlowInfo; 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( return TypeInfoFactoryPackage.createTypeInfo(expressionTypes.isEmpty() ? null : DataFlowUtils.checkType(
CommonSupertypes.commonSupertype(expressionTypes), expression, DataFlowUtils.checkImplicitCast(
contextWithExpectedType, isStatement), CommonSupertypes.commonSupertype(expressionTypes), expression,
contextWithExpectedType, isStatement),
expression, contextWithExpectedType),
commonDataFlowInfo, commonDataFlowInfo,
loopBreakContinuePossible, loopBreakContinuePossible,
contextWithExpectedType.dataFlowInfo); contextWithExpectedType.dataFlowInfo);