[FE] Make whens on expect sealed classes and enums not exhaustive

This commit is contained in:
Dmitriy Novozhilov
2021-02-24 16:44:48 +03:00
parent 1cf73203c7
commit 4222bb9af2
29 changed files with 768 additions and 11 deletions
@@ -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);
@@ -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");