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
|
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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);
|
"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
|
||||||
|
|
||||||
|
|||||||
-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(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)) {
|
||||||
|
|||||||
+5
-8
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user