FIR checker: support DUPLICATE_LABEL_IN_WHEN
Changes from FE1.0: 1. As discussed previously, no expression evaluation happens during this check. 2. FE1.0 doesn't check redundant object comparisons.
This commit is contained in:
committed by
Mikhail Glukhikh
parent
7c6326856b
commit
4915d8dda3
+1
@@ -1063,6 +1063,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
||||
parameter<String>("illegalReason")
|
||||
}
|
||||
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error<PsiElement>(PositioningStrategy.COMMAS)
|
||||
val DUPLICATE_LABEL_IN_WHEN by warning<KtElement>()
|
||||
}
|
||||
|
||||
val CONTEXT_TRACKING by object : DiagnosticGroup("Context tracking") {
|
||||
|
||||
@@ -563,6 +563,7 @@ object FirErrors {
|
||||
val ELSE_MISPLACED_IN_WHEN by error0<KtWhenEntry>(SourceElementPositioningStrategies.ELSE_ENTRY)
|
||||
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error1<KtElement, String>()
|
||||
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error0<PsiElement>(SourceElementPositioningStrategies.COMMAS)
|
||||
val DUPLICATE_LABEL_IN_WHEN by warning0<KtElement>()
|
||||
|
||||
// Context tracking
|
||||
val TYPE_PARAMETER_IS_NOT_AN_EXPRESSION by error1<KtSimpleNameExpression, FirTypeParameterSymbol>()
|
||||
|
||||
+45
-1
@@ -5,12 +5,19 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.checkCondition
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.classKind
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
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.analysis.diagnostics.withSuppressedDiagnostics
|
||||
import org.jetbrains.kotlin.fir.expressions.FirWhenExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirEnumEntrySymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
|
||||
object FirWhenConditionChecker : FirWhenExpressionChecker() {
|
||||
override fun check(expression: FirWhenExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
@@ -21,5 +28,42 @@ object FirWhenConditionChecker : FirWhenExpressionChecker() {
|
||||
checkCondition(condition, context, reporter)
|
||||
}
|
||||
}
|
||||
if (expression.subject != null) {
|
||||
checkDuplicatedLabels(expression, context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDuplicatedLabels(expression: FirWhenExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// The second part of each pair indicates whether the `is` check is positive or negated.
|
||||
val checkedTypes = hashSetOf<Pair<ConeKotlinType, FirOperation>>()
|
||||
val checkedConstants = hashSetOf<Any?>()
|
||||
for (branch in expression.branches) {
|
||||
when (val condition = branch.condition) {
|
||||
is FirEqualityOperatorCall -> {
|
||||
val arguments = condition.arguments
|
||||
if (arguments.size == 2 && arguments[0] is FirWhenSubjectExpression) {
|
||||
val value = when (val targetExpression = arguments[1]) {
|
||||
is FirConstExpression<*> -> targetExpression.value
|
||||
is FirQualifiedAccessExpression -> targetExpression.calleeReference.toResolvedCallableSymbol() as? FirEnumEntrySymbol
|
||||
?: continue
|
||||
is FirResolvedQualifier -> {
|
||||
val classSymbol = targetExpression.symbol ?: continue
|
||||
if (classSymbol.classKind != ClassKind.OBJECT) continue
|
||||
classSymbol.classId
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
if (!checkedConstants.add(value)) {
|
||||
reporter.reportOn(condition.source, FirErrors.DUPLICATE_LABEL_IN_WHEN, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
is FirTypeOperatorCall -> {
|
||||
if (!checkedTypes.add(condition.conversionTypeRef.coneType to condition.operation)) {
|
||||
reporter.reportOn(condition.conversionTypeRef.source, FirErrors.DUPLICATE_LABEL_IN_WHEN, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -143,6 +143,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_TYPE_P
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATION_ERROR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DESERIALIZATION_ERROR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DUPLICATE_LABEL_IN_WHEN
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DYNAMIC_UPPER_BOUND
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EMPTY_RANGE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ENUM_ENTRY_AS_TYPE
|
||||
@@ -1417,6 +1418,7 @@ class FirDefaultErrorMessages {
|
||||
WHEN_MISSING_CASES
|
||||
)
|
||||
map.put(COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, "Deprecated syntax. Use '||' instead of commas in when-condition for 'when' without argument")
|
||||
map.put(DUPLICATE_LABEL_IN_WHEN, "Duplicate label in when")
|
||||
|
||||
// Context tracking
|
||||
map.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", SYMBOL)
|
||||
|
||||
@@ -15,10 +15,10 @@ const val four = 4
|
||||
fun first(arg: Int) = when (arg) {
|
||||
1 -> 2
|
||||
2 -> 3
|
||||
1 -> 4
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>1<!> -> 4
|
||||
4 -> 5
|
||||
1 -> 6
|
||||
2 -> 7
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>1<!> -> 6
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>2<!> -> 7
|
||||
// Error should be here: see KT-11971
|
||||
four -> 8
|
||||
else -> 0
|
||||
@@ -28,8 +28,8 @@ fun second(arg: String): Int {
|
||||
when (arg) {
|
||||
"ABC" -> return 0
|
||||
"DEF" -> return 1
|
||||
"ABC" -> return -1
|
||||
"DEF" -> return -2
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>"ABC"<!> -> return -1
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>"DEF"<!> -> return -2
|
||||
}
|
||||
return 42
|
||||
}
|
||||
@@ -39,8 +39,9 @@ fun third(arg: Any?): Int {
|
||||
null -> return -1
|
||||
is String -> return 0
|
||||
is Double -> return 1
|
||||
is Double -> return 2
|
||||
null -> return 3
|
||||
is <!DUPLICATE_LABEL_IN_WHEN!>Double<!> -> return 2
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>null<!> -> return 3
|
||||
!is String -> return 4
|
||||
else -> return 5
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@ enum class Color { RED, GREEN, BLUE }
|
||||
fun fourth(arg: Color) = when (arg) {
|
||||
Color.RED -> "RED"
|
||||
Color.GREEN -> "GREEN"
|
||||
Color.RED -> "BLUE"
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>Color.RED<!> -> "BLUE"
|
||||
Color.BLUE -> "BLUE"
|
||||
}
|
||||
|
||||
@@ -59,3 +60,11 @@ fun fifth(arg: Any?) = when (arg) {
|
||||
<!ELSE_MISPLACED_IN_WHEN!>else<!> -> ""
|
||||
else -> null
|
||||
}
|
||||
|
||||
object Foo
|
||||
|
||||
fun sixth(arg: Any?) = when (arg) {
|
||||
Foo -> ""
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>Foo<!> -> ""
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ fun third(arg: Any?): Int {
|
||||
is Double -> return 1
|
||||
is <!DUPLICATE_LABEL_IN_WHEN!>Double<!> -> return 2
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>null<!> -> return 3
|
||||
!is String -> return 4
|
||||
else -> return 5
|
||||
}
|
||||
}
|
||||
@@ -59,3 +60,11 @@ fun fifth(arg: Any?) = when (arg) {
|
||||
<!ELSE_MISPLACED_IN_WHEN!>else<!> -> ""
|
||||
<!UNREACHABLE_CODE!>else -> null<!>
|
||||
}
|
||||
|
||||
object Foo
|
||||
|
||||
fun sixth(arg: Any?) = when (arg) {
|
||||
Foo -> ""
|
||||
Foo -> ""
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package test {
|
||||
public fun first(/*0*/ arg: kotlin.Int): kotlin.Int
|
||||
public fun fourth(/*0*/ arg: test.Color): kotlin.String
|
||||
public fun second(/*0*/ arg: kotlin.String): kotlin.Int
|
||||
public fun sixth(/*0*/ arg: kotlin.Any?): kotlin.String?
|
||||
public fun third(/*0*/ arg: kotlin.Any?): kotlin.Int
|
||||
|
||||
public final enum class Color : kotlin.Enum<test.Color> {
|
||||
@@ -30,4 +31,12 @@ package test {
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.Color
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<test.Color>
|
||||
}
|
||||
|
||||
public object Foo {
|
||||
private constructor Foo()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+4
-4
@@ -26,7 +26,7 @@ fun case1() {
|
||||
val z = JavaEnum.Val_1
|
||||
val when2 = <!NO_ELSE_IN_WHEN!>when<!> (z) {
|
||||
JavaEnum.Val_1 -> { }
|
||||
JavaEnum.Val_1 -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>JavaEnum.Val_1<!> -> { }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,7 +37,7 @@ fun case2() {
|
||||
val b = false
|
||||
val when2: Any = <!NO_ELSE_IN_WHEN!>when<!> (b) {
|
||||
false -> { }
|
||||
false -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>false<!> -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fun case3() {
|
||||
val a = false
|
||||
val when2: Any = <!NO_ELSE_IN_WHEN!>when<!> (a) {
|
||||
true -> { }
|
||||
true -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>true<!> -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ fun case4() {
|
||||
val when2 = <!NO_ELSE_IN_WHEN!>when<!> (x){
|
||||
is SClass.A ->{ }
|
||||
is SClass.B ->{ }
|
||||
is SClass.B ->{ }
|
||||
is <!DUPLICATE_LABEL_IN_WHEN!>SClass.B<!> ->{ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+4
-4
@@ -37,7 +37,7 @@ fun case1() {
|
||||
val when3 = when (z) {
|
||||
JavaEnum.Val_1 -> { }
|
||||
JavaEnum.Val_2 -> { }
|
||||
JavaEnum.Val_2 -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>JavaEnum.Val_2<!> -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ fun case2() {
|
||||
}
|
||||
val when3: Any = <!NO_ELSE_IN_WHEN!>when<!> (b) {
|
||||
false -> { }
|
||||
false -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>false<!> -> { }
|
||||
!false -> { }
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ fun case3() {
|
||||
val when3: Any = when (a) {
|
||||
true -> { }
|
||||
false -> { }
|
||||
false -> { }
|
||||
<!DUPLICATE_LABEL_IN_WHEN!>false<!> -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ fun case4() {
|
||||
val when3 = when (x){
|
||||
is SClass.A ->{ }
|
||||
is SClass.B ->{ }
|
||||
is SClass.B ->{ }
|
||||
is <!DUPLICATE_LABEL_IN_WHEN!>SClass.B<!> ->{ }
|
||||
is SClass.C ->{ }
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -2933,6 +2933,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.DUPLICATE_LABEL_IN_WHEN) { firDiagnostic ->
|
||||
DuplicateLabelInWhenImpl(
|
||||
firDiagnostic as FirPsiDiagnostic,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
|
||||
TypeParameterIsNotAnExpressionImpl(
|
||||
firSymbolBuilder.classifierBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir),
|
||||
|
||||
+4
@@ -2050,6 +2050,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
override val diagnosticClass get() = CommaInWhenConditionWithoutArgument::class
|
||||
}
|
||||
|
||||
abstract class DuplicateLabelInWhen : KtFirDiagnostic<KtElement>() {
|
||||
override val diagnosticClass get() = DuplicateLabelInWhen::class
|
||||
}
|
||||
|
||||
abstract class TypeParameterIsNotAnExpression : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||
override val diagnosticClass get() = TypeParameterIsNotAnExpression::class
|
||||
abstract val typeParameter: KtTypeParameterSymbol
|
||||
|
||||
+7
@@ -3305,6 +3305,13 @@ internal class CommaInWhenConditionWithoutArgumentImpl(
|
||||
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class DuplicateLabelInWhenImpl(
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.DuplicateLabelInWhen(), KtAbstractFirDiagnostic<KtElement> {
|
||||
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class TypeParameterIsNotAnExpressionImpl(
|
||||
override val typeParameter: KtTypeParameterSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
|
||||
Reference in New Issue
Block a user