diff --git a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingBooleanBranch.kt b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingBooleanBranch.kt index ddf84b51870..30b5e388410 100644 --- a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingBooleanBranch.kt +++ b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingBooleanBranch.kt @@ -27,7 +27,7 @@ fun test_2(cond: Boolean?) { } fun test_3(cond: Boolean) { - when (cond) { + when (cond) { true -> 1 } } diff --git a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingEnumEntry.kt b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingEnumEntry.kt index 75355434441..eda7c7e5889 100644 --- a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingEnumEntry.kt +++ b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingEnumEntry.kt @@ -27,7 +27,7 @@ fun test_2(enum: SomeEnum?) { } fun test_3(enum: SomeEnum) { - when (enum) { + when (enum) { SomeEnum.A -> 1 } } diff --git a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingSealedInheritor.kt b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingSealedInheritor.kt index 08cab15d1db..4649a6dd78c 100644 --- a/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingSealedInheritor.kt +++ b/compiler/fir/analysis-tests/testData/resolve/exhaustiveness/negative/missingSealedInheritor.kt @@ -44,7 +44,7 @@ fun test_2(base: Base?) { } fun test_3(base: Base) { - when (base) { + when (base) { is A -> 1 } } diff --git a/compiler/fir/analysis-tests/testData/resolve/types/bareWithSubjectTypeAlias.kt b/compiler/fir/analysis-tests/testData/resolve/types/bareWithSubjectTypeAlias.kt index 536f93fee4b..c445d71b785 100644 --- a/compiler/fir/analysis-tests/testData/resolve/types/bareWithSubjectTypeAlias.kt +++ b/compiler/fir/analysis-tests/testData/resolve/types/bareWithSubjectTypeAlias.kt @@ -8,7 +8,7 @@ typealias TA = A fun bar(): TA = TODO() fun foo() { - when (val a = bar()) { + when (val a = bar()) { is A.B -> a.x.length } } diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index f839c7b217a..7b754d1e82f 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -976,6 +976,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val NO_ELSE_IN_WHEN by error(PositioningStrategy.WHEN_EXPRESSION) { parameter>("missingWhenCases") } + val NON_EXHAUSTIVE_WHEN_STATEMENT by warning(PositioningStrategy.WHEN_EXPRESSION) { + parameter("type") + parameter>("missingWhenCases") + } val INVALID_IF_AS_EXPRESSION by error(PositioningStrategy.IF_EXPRESSION) val ELSE_MISPLACED_IN_WHEN by error(PositioningStrategy.ELSE_ENTRY) val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index c71ec33603e..07e9152408d 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -516,6 +516,7 @@ object FirErrors { // When expressions val EXPECTED_CONDITION by error0() val NO_ELSE_IN_WHEN by error1>(SourceElementPositioningStrategies.WHEN_EXPRESSION) + val NON_EXHAUSTIVE_WHEN_STATEMENT by warning2>(SourceElementPositioningStrategies.WHEN_EXPRESSION) val INVALID_IF_AS_EXPRESSION by error0(SourceElementPositioningStrategies.IF_EXPRESSION) val ELSE_MISPLACED_IN_WHEN by error0(SourceElementPositioningStrategies.ELSE_ENTRY) val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error1() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExhaustiveWhenChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExhaustiveWhenChecker.kt index 2c91827197c..7b1254239c1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExhaustiveWhenChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirExhaustiveWhenChecker.kt @@ -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 + 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 diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 3964a5faec7..83864e9ce78 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -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) diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt index 0629b940beb..da908fc1b5a 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt @@ -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): Boolean { diff --git a/compiler/testData/diagnostics/tests/j+k/kt2619.fir.kt b/compiler/testData/diagnostics/tests/j+k/kt2619.fir.kt deleted file mode 100644 index cd19e54d1eb..00000000000 --- a/compiler/testData/diagnostics/tests/j+k/kt2619.fir.kt +++ /dev/null @@ -1,15 +0,0 @@ -//FILE: foo.kt -fun main() { - val c: Type - when (c) { - - } -} - - - -//FILE: Type.java -public enum Type { - TYPE, - NO_TYPE; -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/j+k/kt2619.kt b/compiler/testData/diagnostics/tests/j+k/kt2619.kt index 62359d42ae7..f5019e77cf0 100644 --- a/compiler/testData/diagnostics/tests/j+k/kt2619.kt +++ b/compiler/testData/diagnostics/tests/j+k/kt2619.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL //FILE: foo.kt fun main() { val c: Type diff --git a/compiler/testData/diagnostics/tests/reassignment/when.fir.kt b/compiler/testData/diagnostics/tests/reassignment/when.fir.kt deleted file mode 100644 index 65a9d862ece..00000000000 --- a/compiler/testData/diagnostics/tests/reassignment/when.fir.kt +++ /dev/null @@ -1,10 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_VALUE - -fun foo(f: Boolean): Int { - val i: Int - when (f) { - true -> i = 1 - } - i = 3 - return i -} diff --git a/compiler/testData/diagnostics/tests/reassignment/when.kt b/compiler/testData/diagnostics/tests/reassignment/when.kt index 26ef1b78001..550507f09f4 100644 --- a/compiler/testData/diagnostics/tests/reassignment/when.kt +++ b/compiler/testData/diagnostics/tests/reassignment/when.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_VALUE fun foo(f: Boolean): Int { diff --git a/compiler/testData/diagnostics/tests/smartCasts/propertyAsCondition.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/propertyAsCondition.fir.kt index bb0861c837a..cccaea15671 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/propertyAsCondition.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/propertyAsCondition.fir.kt @@ -7,7 +7,7 @@ fun foo(my: My) { if (my.x != null) { // my.x should be smart-cast if (my.x) doIt() - when (my.x) { + when (my.x) { true -> doIt() } when { @@ -20,11 +20,11 @@ fun bar(x: Boolean?) { if (x != null) { // x should be smart-cast if (x) doIt() - when (x) { + when (x) { true -> doIt() } when { x -> doIt() } } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.fir.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.fir.kt index 6cce80a0f93..af34594898a 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.fir.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.fir.kt @@ -27,7 +27,7 @@ fun bar(a: Boolean, b: Boolean): Int { if (a) { x = 1 } - when (b) { + when (b) { false -> x = 3 } return x diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.fir.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.fir.kt index be1be855c35..81c370ef2b3 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.fir.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.fir.kt @@ -26,7 +26,7 @@ fun bar(a: Boolean, b: Boolean): Int { if (a) { x = 1 } - when (b) { + when (b) { false -> x = 3 } return x diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.fir.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.fir.kt deleted file mode 100644 index 4786062e25d..00000000000 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE) - * - * SPEC VERSION: 0.1-152 - * PRIMARY LINKS: expressions, when-expression -> paragraph 5 -> sentence 1 - * expressions, when-expression, exhaustive-when-expressions -> paragraph 1 -> sentence 1 - * expressions, when-expression, exhaustive-when-expressions -> paragraph 2 -> sentence 9 - * expressions, when-expression -> paragraph 9 -> sentence 2 - * control--and-data-flow-analysis, performing-analysis-on-the-control-flow-graph, variable-initialization-analysis -> paragraph 2 -> sentence 1 - * control--and-data-flow-analysis, performing-analysis-on-the-control-flow-graph, variable-initialization-analysis -> paragraph 2 -> sentence 3 - */ - -// Base for KT-6227 -enum class X { A, B, C, D } - -fun foo(arg: X): String { - var res = "XXX" - when (arg) { - X.A -> res = "A" - X.B -> res = "B" - } - return res -} diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.kt index 3dfba4f220c..4ab8accdd14 100644 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.kt +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarning.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL /* * KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE) * diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningForSealedClass.fir.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningForSealedClass.fir.kt index 02c0d4d56b0..a954203c162 100644 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningForSealedClass.fir.kt +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningForSealedClass.fir.kt @@ -22,7 +22,7 @@ object Last : S() fun use(s: String) = s fun foo(s: S) { - when (s) { + when (s) { First -> {} is Derived -> use(s.s) } diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.fir.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.fir.kt deleted file mode 100644 index 2d3da1ec811..00000000000 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.fir.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE) - * - * SPEC VERSION: 0.1-152 - * PRIMARY LINKS: expressions, when-expression -> paragraph 5 -> sentence 1 - * expressions, when-expression, exhaustive-when-expressions -> paragraph 1 -> sentence 1 - * expressions, when-expression, exhaustive-when-expressions -> paragraph 2 -> sentence 9 - * expressions, when-expression, exhaustive-when-expressions -> paragraph 2 -> sentence 10 - * expressions, when-expression -> paragraph 9 -> sentence 2 - * control--and-data-flow-analysis, performing-analysis-on-the-control-flow-graph, variable-initialization-analysis -> paragraph 2 -> sentence 1 - * control--and-data-flow-analysis, performing-analysis-on-the-control-flow-graph, variable-initialization-analysis -> paragraph 2 -> sentence 3 - */ - -// Base for KT-6227 -enum class X { A, B, C, D } - -fun foo(arg: X?): String { - var res = "XXX" - // Should we report something here? Probably not, null is not an enum entry - when (arg) { - X.A -> res = "A" - X.B -> res = "B" - X.C -> res = "C" - X.D -> res = "D" - } - return res -} diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.kt index b62ffae37ce..ef5adf6f92e 100644 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.kt +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningNull.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL /* * KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE) * diff --git a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.fir.kt b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.fir.kt deleted file mode 100644 index d93e09154b8..00000000000 --- a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.fir.kt +++ /dev/null @@ -1,154 +0,0 @@ -// !LANGUAGE: -ProhibitNonExhaustiveWhenOnLogicalTypes - -enum class SomeEnum { - A, B -} - -sealed class Base { - class A : Base() - class B : Base() -} - -sealed interface IBase { - interface A : IBase - interface B : IBase -} - -// ------------------ not null ------------------ - -fun test_1(x: SomeEnum) { - when (x) { - SomeEnum.A -> "" - } -} - -fun test_2(x: Base) { - when (x) { - is Base.A -> "" - } -} - -fun test_3(x: IBase) { - when (x) { - is IBase.A -> "" - } -} - -fun test_4(x: Boolean) { - when (x) { - true -> "" - } -} - -// ------------------ nullable ------------------ - -fun test_5(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - SomeEnum.B -> "" - } - - when (x) { - SomeEnum.A -> "" - null -> "" - } -} - -fun test_6(x: Base?) { - when (x) { - is Base.A -> "" - is Base.B -> "" - } - - when (x) { - is Base.A -> "" - null -> "" - } -} - -fun test_7(x: IBase?) { - when (x) { - is IBase.A -> "" - is IBase.B -> "" - } - when (x) { - is IBase.A -> "" - null -> "" - } -} - -fun test_8(x: Boolean?) { - when (x) { - true -> "" - false -> "" - } - - when (x) { - true -> "" - null -> "" - } -} - -// ------------------ with else ------------------ - -fun test_9(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - else -> "" - } -} - -fun test_10(x: Base?) { - when (x) { - is Base.A -> "" - else -> "" - } -} - -fun test_11(x: IBase?) { - when (x) { - is IBase.A -> "" - else -> "" - } -} - -fun test_12(x: Boolean?) { - when (x) { - true -> "" - else -> "" - } -} - -// ------------------ exhaustive ------------------ - -fun test_13(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - SomeEnum.B -> "" - null -> "" - } -} - -fun test_14(x: Base?) { - when (x) { - is Base.A -> "" - is Base.B -> "" - null -> "" - } -} - -fun test_15(x: IBase?) { - when (x) { - is IBase.A -> "" - is IBase.B -> "" - null -> "" - } -} - -fun test_16(x: Boolean?) { - when (x) { - true -> "" - false -> "" - null -> "" - } -} diff --git a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.kt b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.kt index c6b9ecec650..e61cf172d20 100644 --- a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.kt +++ b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_6.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -ProhibitNonExhaustiveWhenOnAlgebraicTypes enum class SomeEnum { diff --git a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.fir.kt b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.fir.kt deleted file mode 100644 index 42d35b9098e..00000000000 --- a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.fir.kt +++ /dev/null @@ -1,154 +0,0 @@ -// !LANGUAGE: +ProhibitNonExhaustiveWhenOnLogicalTypes - -enum class SomeEnum { - A, B -} - -sealed class Base { - class A : Base() - class B : Base() -} - -sealed interface IBase { - interface A : IBase - interface B : IBase -} - -// ------------------ not null ------------------ - -fun test_1(x: SomeEnum) { - when (x) { - SomeEnum.A -> "" - } -} - -fun test_2(x: Base) { - when (x) { - is Base.A -> "" - } -} - -fun test_3(x: IBase) { - when (x) { - is IBase.A -> "" - } -} - -fun test_4(x: Boolean) { - when (x) { - true -> "" - } -} - -// ------------------ nullable ------------------ - -fun test_5(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - SomeEnum.B -> "" - } - - when (x) { - SomeEnum.A -> "" - null -> "" - } -} - -fun test_6(x: Base?) { - when (x) { - is Base.A -> "" - is Base.B -> "" - } - - when (x) { - is Base.A -> "" - null -> "" - } -} - -fun test_7(x: IBase?) { - when (x) { - is IBase.A -> "" - is IBase.B -> "" - } - when (x) { - is IBase.A -> "" - null -> "" - } -} - -fun test_8(x: Boolean?) { - when (x) { - true -> "" - false -> "" - } - - when (x) { - true -> "" - null -> "" - } -} - -// ------------------ with else ------------------ - -fun test_9(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - else -> "" - } -} - -fun test_10(x: Base?) { - when (x) { - is Base.A -> "" - else -> "" - } -} - -fun test_11(x: IBase?) { - when (x) { - is IBase.A -> "" - else -> "" - } -} - -fun test_12(x: Boolean?) { - when (x) { - true -> "" - else -> "" - } -} - -// ------------------ exhaustive ------------------ - -fun test_13(x: SomeEnum?) { - when (x) { - SomeEnum.A -> "" - SomeEnum.B -> "" - null -> "" - } -} - -fun test_14(x: Base?) { - when (x) { - is Base.A -> "" - is Base.B -> "" - null -> "" - } -} - -fun test_15(x: IBase?) { - when (x) { - is IBase.A -> "" - is IBase.B -> "" - null -> "" - } -} - -fun test_16(x: Boolean?) { - when (x) { - true -> "" - false -> "" - null -> "" - } -} diff --git a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.kt b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.kt index dc62a7ea2e9..64fbe139faa 100644 --- a/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.kt +++ b/compiler/testData/diagnostics/tests/when/nonExhaustiveWhenStatement_1_7.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: +ProhibitNonExhaustiveWhenOnAlgebraicTypes enum class SomeEnum { diff --git a/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.fir.kt deleted file mode 100644 index 695bbb0f36b..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.fir.kt +++ /dev/null @@ -1,20 +0,0 @@ -fun test1() { - if (true) { - when (true) { - true -> println() - } - } else { - System.out?.println() // kotlin.Unit? - } -} - -fun test2() { - val mlist = arrayListOf("") - if (true) { - when (true) { - true -> println() - } - } else { - mlist.add("") // kotlin.Boolean - } -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.kt b/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.kt index 44296b83419..8e8e9e94a07 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/when/kt10192.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL fun test1() { if (true) { when (true) { diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt index 0c369265a26..65a3877b202 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt @@ -60,11 +60,11 @@ fun case_5(value_1: Int, value_2: Int, value_3: Boolean?) { false -> "2" null -> "3" } - value_1 == 5 -> when (value_3) { + value_1 == 5 -> when (value_3) { true -> "1" false -> "2" } - value_1 == 6 -> when (value_3) {} + value_1 == 6 -> when (value_3) {} } } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt index fdbcdc52f78..270563b5d83 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt @@ -51,21 +51,21 @@ fun case_5(value_1: Int, value_2: Int, value_3: Boolean?) { value_2 > 100 -> "2" else -> "3" } - 2 -> when (value_3) { + 2 -> when (value_3) { value_2 > 1000 -> "1" value_2 > 100 -> "2" } - 3 -> when (value_3) {} + 3 -> when (value_3) {} 4 -> when (value_3) { true -> "1" false -> "2" null -> "3" } - 5 -> when (value_3) { + 5 -> when (value_3) { true -> "1" false -> "2" } - 6 -> when (value_3) {} + 6 -> when (value_3) {} } } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt index b64e68d5dfc..696b3985b82 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt @@ -24,7 +24,7 @@ fun case_2(value_1: Number, value_2: Int) { // TESTCASE NUMBER: 3 fun case_3(value_1: Boolean, value_2: Boolean, value_3: Long) { - when (value_1) { + when (value_1) { value_2 -> {} !value_2 -> {} getBoolean() && value_2 -> {} diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt index 897e4cc24dd..78e7dd7fc9d 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt @@ -18,7 +18,7 @@ fun case_2(value_1: Number, value_2: Int) { // TESTCASE NUMBER: 3 fun case_3(value_1: Boolean, value_2: Boolean, value_3: Long) { - when (value_1) { + when (value_1) { value_2, !value_2, getBoolean() && value_2, getChar() != 'a' -> {} getList() === getAny(), value_3 <= 11 -> {} } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/controlFlow/initialization/neg/3.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/controlFlow/initialization/neg/3.fir.kt index 83b6679bee6..f8b54f4f83f 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/controlFlow/initialization/neg/3.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/controlFlow/initialization/neg/3.fir.kt @@ -6,7 +6,7 @@ fun case_1(value_1: EnumClass?) { val value_2: Int - when (value_1) { + when (value_1) { EnumClass.NORTH -> funWithExactlyOnceCallsInPlace { value_2 = 1 } EnumClass.SOUTH -> funWithExactlyOnceCallsInPlace { value_2 = 2 } EnumClass.EAST -> funWithExactlyOnceCallsInPlace { value_2 = 4 } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt index 71525dd149a..cb89a82bbfd 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt @@ -100,7 +100,7 @@ fun case_4(value_1: Number, value_2: (() -> Unit)?) { // TESTCASE NUMBER: 5 fun case_5(value_1: Number?, value_2: String?) { - when (value_2.case_5(value_1)) { + when (value_2.case_5(value_1)) { true -> { println(value_2.length) println(value_1.toByte()) diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt index 2b7a7ac6eaf..cdd422ac0ef 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt @@ -105,7 +105,7 @@ fun case_4(value_1: Number, value_2: (() -> Unit)?) { * ISSUES: KT-26612 */ fun case_5(value_1: Number?, value_2: String?) { - when (value_2.case_5(value_1)) { + when (value_2.case_5(value_1)) { true -> { println(value_2.length) println(value_1.toByte()) diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/54.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/54.fir.kt index 38bdee11f95..ad56e982876 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/54.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/54.fir.kt @@ -137,7 +137,7 @@ fun case_7() { var b = a b b.length - when (true) { + when (true) { true -> b = a } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 1133def9ef5..eab3afebcbe 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -2649,6 +2649,16 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.NON_EXHAUSTIVE_WHEN_STATEMENT) { firDiagnostic -> + NonExhaustiveWhenStatementImpl( + firDiagnostic.a, + firDiagnostic.b.map { whenMissingCase -> + whenMissingCase + }, + firDiagnostic as FirPsiDiagnostic, + token, + ) + } add(FirErrors.INVALID_IF_AS_EXPRESSION) { firDiagnostic -> InvalidIfAsExpressionImpl( firDiagnostic as FirPsiDiagnostic, diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 28a44f4cb23..0a927b6e675 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -1855,6 +1855,12 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val missingWhenCases: List } + abstract class NonExhaustiveWhenStatement : KtFirDiagnostic() { + override val diagnosticClass get() = NonExhaustiveWhenStatement::class + abstract val type: String + abstract val missingWhenCases: List + } + abstract class InvalidIfAsExpression : KtFirDiagnostic() { override val diagnosticClass get() = InvalidIfAsExpression::class } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 48d176576b2..98b993e30db 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -2990,6 +2990,15 @@ internal class NoElseInWhenImpl( override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) } +internal class NonExhaustiveWhenStatementImpl( + override val type: String, + override val missingWhenCases: List, + firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NonExhaustiveWhenStatement(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) +} + internal class InvalidIfAsExpressionImpl( firDiagnostic: FirPsiDiagnostic, override val token: ValidityToken,