[FIR] Report NON_EXHAUSTIVE_WHEN_STATEMENT/NO_ELSE_IN_WHEN for when's on logical types

^KT-47709 In Progress
This commit is contained in:
Dmitriy Novozhilov
2021-07-13 13:15:49 +03:00
committed by teamcityserver
parent ef635f6a96
commit a6edd852ff
38 changed files with 120 additions and 431 deletions
@@ -27,7 +27,7 @@ fun test_2(cond: Boolean?) {
}
fun test_3(cond: Boolean) {
when (cond) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (cond) {
true -> 1
}
}
@@ -27,7 +27,7 @@ fun test_2(enum: SomeEnum?) {
}
fun test_3(enum: SomeEnum) {
when (enum) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (enum) {
SomeEnum.A -> 1
}
}
@@ -44,7 +44,7 @@ fun test_2(base: Base?) {
}
fun test_3(base: Base) {
when (base) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (base) {
is A -> 1
}
}
@@ -8,7 +8,7 @@ typealias TA = A<CharSequence>
fun bar(): TA = TODO()
fun foo() {
when (val a = bar()) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (val a = bar()) {
is A.B -> a.x.length
}
}
@@ -976,6 +976,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val NO_ELSE_IN_WHEN by error<KtWhenExpression>(PositioningStrategy.WHEN_EXPRESSION) {
parameter<List<WhenMissingCase>>("missingWhenCases")
}
val NON_EXHAUSTIVE_WHEN_STATEMENT by warning<KtWhenExpression>(PositioningStrategy.WHEN_EXPRESSION) {
parameter<String>("type")
parameter<List<WhenMissingCase>>("missingWhenCases")
}
val INVALID_IF_AS_EXPRESSION by error<KtIfExpression>(PositioningStrategy.IF_EXPRESSION)
val ELSE_MISPLACED_IN_WHEN by error<KtWhenEntry>(PositioningStrategy.ELSE_ENTRY)
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error<KtElement> {
@@ -516,6 +516,7 @@ object FirErrors {
// When expressions
val EXPECTED_CONDITION by error0<KtWhenCondition>()
val NO_ELSE_IN_WHEN by error1<KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
val NON_EXHAUSTIVE_WHEN_STATEMENT by warning2<KtWhenExpression, String, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
val INVALID_IF_AS_EXPRESSION by error0<KtIfExpression>(SourceElementPositioningStrategies.IF_EXPRESSION)
val ELSE_MISPLACED_IN_WHEN by error0<KtWhenEntry>(SourceElementPositioningStrategies.ELSE_ENTRY)
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error1<KtElement, String>()
@@ -6,29 +6,76 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.expressions.ExhaustivenessStatus
import org.jetbrains.kotlin.fir.expressions.FirWhenExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
import org.jetbrains.kotlin.fir.expressions.isExhaustive
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isBooleanOrNullableBoolean
object FirExhaustiveWhenChecker : FirWhenExpressionChecker() {
override fun check(expression: FirWhenExpression, context: CheckerContext, reporter: DiagnosticReporter) {
if (expression.usedAsExpression && !expression.isExhaustive) {
val source = expression.source ?: return
reportNotExhaustive(expression, context, reporter)
reportElseMisplaced(expression, reporter, context)
}
private fun reportNotExhaustive(whenExpression: FirWhenExpression, context: CheckerContext, reporter: DiagnosticReporter) {
if (whenExpression.isExhaustive) return
val source = whenExpression.source ?: return
if (whenExpression.usedAsExpression) {
if (source.isIfExpression) {
reporter.reportOn(source, FirErrors.INVALID_IF_AS_EXPRESSION, context)
return
} else if (source.isWhenExpression) {
val missingCases = (expression.exhaustivenessStatus as ExhaustivenessStatus.NotExhaustive).reasons
reporter.reportOn(source, FirErrors.NO_ELSE_IN_WHEN, missingCases, context)
reporter.reportOn(source, FirErrors.NO_ELSE_IN_WHEN, whenExpression.missingCases, context)
}
} else {
val subjectType = whenExpression.subject?.typeRef?.coneType ?: return
val subjectClassSymbol = subjectType.fullyExpandedType(context.session).toRegularClassSymbol(context.session) ?: return
val kind = when {
subjectClassSymbol.modality == Modality.SEALED -> AlgebraicTypeKind.Sealed
subjectClassSymbol.classKind == ClassKind.ENUM_CLASS -> AlgebraicTypeKind.Enum
subjectType.isBooleanOrNullableBoolean -> AlgebraicTypeKind.Boolean
else -> return
}
if (context.session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitNonExhaustiveWhenOnAlgebraicTypes)) {
reporter.reportOn(source, FirErrors.NO_ELSE_IN_WHEN, whenExpression.missingCases, context)
} else {
reporter.reportOn(source, FirErrors.NON_EXHAUSTIVE_WHEN_STATEMENT, kind.displayName, whenExpression.missingCases, context)
}
}
}
private val FirWhenExpression.missingCases: List<WhenMissingCase>
get() = (exhaustivenessStatus as ExhaustivenessStatus.NotExhaustive).reasons
private enum class AlgebraicTypeKind(val displayName: String) {
Sealed("sealed class/interface"),
Enum("enum"),
Boolean("Boolean")
}
private fun reportElseMisplaced(
expression: FirWhenExpression,
reporter: DiagnosticReporter,
context: CheckerContext
) {
val branchesCount = expression.branches.size
for (indexedValue in expression.branches.withIndex()) {
val branch = indexedValue.value
@@ -249,6 +249,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NEWER_VERSION_IN_
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NEXT_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NONE_APPLICABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_ABSTRACT_FUNCTION_WITH_NO_BODY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_EXHAUSTIVE_WHEN_STATEMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_FINAL_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED
@@ -1284,6 +1285,12 @@ class FirDefaultErrorMessages {
map.put(EXPECTED_CONDITION, "Expected condition of type Boolean")
map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES)
map.put(INVALID_IF_AS_EXPRESSION, "'if' must have both main and 'else' branches if used as an expression")
map.put(
NON_EXHAUSTIVE_WHEN_STATEMENT,
"Non exhaustive ''when'' statements on {0} will be prohibited in 1.7, add {1}",
TO_STRING,
WHEN_MISSING_CASES
)
// Context tracking
map.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", SYMBOL)
@@ -63,6 +63,7 @@ val ConeKotlinType.isNothing: Boolean get() = isBuiltinType(StandardClassIds.Not
val ConeKotlinType.isNullableNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, true)
val ConeKotlinType.isUnit: Boolean get() = isBuiltinType(StandardClassIds.Unit, false)
val ConeKotlinType.isBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, false)
val ConeKotlinType.isBooleanOrNullableBoolean: Boolean get() = isAnyOfBuiltinType(setOf(StandardClassIds.Boolean))
val ConeKotlinType.isEnum: Boolean get() = isBuiltinType(StandardClassIds.Enum, false)
val ConeKotlinType.isString: Boolean get() = isBuiltinType(StandardClassIds.String, false)
val ConeKotlinType.isPrimitiveOrNullablePrimitive: Boolean get() = isAnyOfBuiltinType(StandardClassIds.primitiveTypes)
@@ -85,9 +86,9 @@ val ConeKotlinType.isUnsignedType: Boolean get() = isUnsignedTypeOrNullableUnsig
private val builtinIntegerTypes = setOf(StandardClassIds.Int, StandardClassIds.Byte, StandardClassIds.Long, StandardClassIds.Short)
val ConeKotlinType.isIntegerTypeOrNullableIntegerTypeOfAnySize: Boolean get() = isAnyOfBuiltinType(builtinIntegerTypes)
private fun ConeKotlinType.isBuiltinType(classId: ClassId, isNullable: Boolean): Boolean {
private fun ConeKotlinType.isBuiltinType(classId: ClassId, isNullable: Boolean?): Boolean {
if (this !is ConeClassLikeType) return false
return lookupTag.classId == classId && type.isNullable == isNullable
return lookupTag.classId == classId && (isNullable == null || type.isNullable == isNullable)
}
private fun ConeKotlinType.isAnyOfBuiltinType(classIds: Set<ClassId>): Boolean {