Reorganization of when checking: exhaustion predicates clarified, analysis on platform enum warnings was moved to frontend.java
This commit is contained in:
+17
-1
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
|
||||
-1
@@ -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)) {
|
||||
|
||||
+5
-8
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user