diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt index a521cc53a70..59b4b497e15 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt @@ -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 { } } } -} \ No newline at end of file +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index a26a03aa2c5..e8007a76840 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -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"); } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 9c69e64a07f..ec66ca22976 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -79,6 +79,8 @@ public interface ErrorsJvm { DiagnosticFactory2 NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory0 WHEN_ENUM_CAN_BE_NULL_IN_JAVA = DiagnosticFactory0.create(WARNING); + @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java index 711fc310381..2fe0360422f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java @@ -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) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index bc1aac565dc..d17fb7e6d2d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -618,7 +618,6 @@ public interface Errors { DiagnosticFactory0 EXPECTED_CONDITION = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY); DiagnosticFactory0 NO_ELSE_IN_WHEN = DiagnosticFactory0.create(ERROR, WHEN_EXPRESSION); - DiagnosticFactory0 WHEN_ENUM_CAN_BE_NULL_IN_JAVA = DiagnosticFactory0.create(WARNING); // Type mismatch diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 170195ccce1..ab68afa0650 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java index 6365cca816e..e0e87051ae4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowUtils.java @@ -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)) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.java index c6f362812db..a07f8068bb6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.java @@ -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);