[FE] Make whens on expect sealed classes and enums not exhaustive
This commit is contained in:
+5
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.*
|
||||
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
|
||||
import org.jetbrains.kotlin.idea.MainFunctionDetector
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -1003,6 +1004,10 @@ class ControlFlowInformationProviderImpl private constructor(
|
||||
if (usedAsExpression && missingCases.isNotEmpty()) {
|
||||
if (elseEntry != null) continue
|
||||
trace.report(NO_ELSE_IN_WHEN.on(element, missingCases))
|
||||
missingCases.firstOrNull { it is WhenMissingCase.ConditionTypeIsExpect }?.let {
|
||||
require(it is WhenMissingCase.ConditionTypeIsExpect)
|
||||
trace.report(EXPECT_TYPE_IN_WHEN_WITHOUT_ELSE.on(element, it.typeOfDeclaration))
|
||||
}
|
||||
} else if (subjectExpression != null) {
|
||||
val subjectType = trace.getType(subjectExpression)
|
||||
if (elseEntry != null) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.cfg
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.cfg.WhenOnEnumExhaustivenessChecker.enumEntries
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
@@ -40,11 +41,13 @@ import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluat
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.runIf
|
||||
import java.util.*
|
||||
|
||||
|
||||
val List<WhenMissingCase>.hasUnknown: Boolean
|
||||
get() = firstOrNull() == WhenMissingCase.Unknown
|
||||
get() = any { it == WhenMissingCase.Unknown || it is WhenMissingCase.ConditionTypeIsExpect }
|
||||
|
||||
private interface WhenExhaustivenessChecker {
|
||||
fun getMissingCases(
|
||||
@@ -57,6 +60,21 @@ private interface WhenExhaustivenessChecker {
|
||||
fun isApplicable(subjectType: KotlinType): Boolean = false
|
||||
}
|
||||
|
||||
// It's not a regular exhaustiveness checker, invoke it only inside other checkers
|
||||
private object WhenOnExpectExhaustivenessChecker {
|
||||
fun getMissingCase(subjectDescriptor: ClassDescriptor?): WhenMissingCase? {
|
||||
return runIf(subjectDescriptor?.isExpect == true) {
|
||||
when (subjectDescriptor!!.kind) {
|
||||
ClassKind.CLASS -> WhenMissingCase.ConditionTypeIsExpect.SealedClass
|
||||
ClassKind.INTERFACE -> WhenMissingCase.ConditionTypeIsExpect.SealedInterface
|
||||
ClassKind.ENUM_CLASS -> WhenMissingCase.ConditionTypeIsExpect.Enum
|
||||
else -> WhenMissingCase.Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// It's not a regular exhaustiveness checker, invoke it only inside other checkers
|
||||
private object WhenOnNullableExhaustivenessChecker /* : WhenExhaustivenessChecker*/ {
|
||||
fun getMissingCases(expression: KtWhenExpression, context: BindingContext, nullable: Boolean) =
|
||||
@@ -209,6 +227,7 @@ internal abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChe
|
||||
}
|
||||
|
||||
private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecker() {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun getMissingCases(
|
||||
expression: KtWhenExpression,
|
||||
context: BindingContext,
|
||||
@@ -216,8 +235,11 @@ private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecke
|
||||
nullable: Boolean
|
||||
): List<WhenMissingCase> {
|
||||
assert(isEnumClass(subjectDescriptor)) { "isWhenOnEnumExhaustive should be called with an enum class descriptor" }
|
||||
return getMissingClassCases(expression, subjectDescriptor!!.enumEntries, context) +
|
||||
WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, nullable)
|
||||
return buildList {
|
||||
addAll(getMissingClassCases(expression, subjectDescriptor!!.enumEntries, context))
|
||||
addAll(WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, nullable))
|
||||
addIfNotNull(WhenOnExpectExhaustivenessChecker.getMissingCase(subjectDescriptor))
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicable(subjectType: KotlinType): Boolean {
|
||||
@@ -226,7 +248,7 @@ private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecke
|
||||
}
|
||||
|
||||
internal object WhenOnSealedExhaustivenessChecker : WhenOnClassExhaustivenessChecker() {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun getMissingCases(
|
||||
expression: KtWhenExpression,
|
||||
context: BindingContext,
|
||||
@@ -238,8 +260,11 @@ internal object WhenOnSealedExhaustivenessChecker : WhenOnClassExhaustivenessChe
|
||||
}
|
||||
|
||||
val allSubclasses = subjectDescriptor!!.deepSealedSubclasses
|
||||
return getMissingClassCases(expression, allSubclasses.toSet(), context) +
|
||||
WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, nullable)
|
||||
return buildList {
|
||||
addAll(getMissingClassCases(expression, allSubclasses.toSet(), context))
|
||||
addAll(WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, nullable))
|
||||
addIfNotNull(WhenOnExpectExhaustivenessChecker.getMissingCase(subjectDescriptor))
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicable(subjectType: KotlinType): Boolean {
|
||||
|
||||
@@ -1057,6 +1057,7 @@ public interface Errors {
|
||||
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>> NON_EXHAUSTIVE_WHEN = DiagnosticFactory1.create(WARNING, WHEN_EXPRESSION);
|
||||
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>>
|
||||
NON_EXHAUSTIVE_WHEN_ON_SEALED_CLASS = DiagnosticFactory1.create(INFO, WHEN_EXPRESSION);
|
||||
DiagnosticFactory1<KtWhenExpression, String> EXPECT_TYPE_IN_WHEN_WITHOUT_ELSE = DiagnosticFactory1.create(ERROR, WHEN_EXPRESSION);
|
||||
|
||||
DiagnosticFactory0<PsiElement> COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> DUPLICATE_LABEL_IN_WHEN = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
+1
@@ -610,6 +610,7 @@ public class DefaultErrorMessages {
|
||||
MAP.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", RENDER_WHEN_MISSING_CASES);
|
||||
MAP.put(NON_EXHAUSTIVE_WHEN, "''when'' expression on enum is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES);
|
||||
MAP.put(NON_EXHAUSTIVE_WHEN_ON_SEALED_CLASS, "''when'' expression on sealed classes is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES);
|
||||
MAP.put(EXPECT_TYPE_IN_WHEN_WITHOUT_ELSE, "'when' with expect {0} as subject can not be exhaustive without else branch", STRING);
|
||||
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user