FIR checker: report EXPECTED_CONDITION

This commit is contained in:
Tianyu Geng
2021-06-18 02:12:17 +03:00
committed by Mikhail Glukhikh
parent e396da0562
commit 31573a98e8
11 changed files with 76 additions and 16 deletions
@@ -867,6 +867,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
} }
val WHEN_EXPRESSIONS by object : DiagnosticGroup("When expressions") { val WHEN_EXPRESSIONS by object : DiagnosticGroup("When expressions") {
val EXPECTED_CONDITION by error<KtWhenCondition>()
val NO_ELSE_IN_WHEN by error<KtWhenExpression>(PositioningStrategy.WHEN_EXPRESSION) { val NO_ELSE_IN_WHEN by error<KtWhenExpression>(PositioningStrategy.WHEN_EXPRESSION) {
parameter<List<WhenMissingCase>>("missingWhenCases") parameter<List<WhenMissingCase>>("missingWhenCases")
} }
@@ -63,6 +63,7 @@ import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
@@ -475,6 +476,7 @@ object FirErrors {
val USELESS_IS_CHECK by warning1<KtElement, Boolean>() val USELESS_IS_CHECK by warning1<KtElement, Boolean>()
// When expressions // When expressions
val EXPECTED_CONDITION by error0<KtWhenCondition>()
val NO_ELSE_IN_WHEN by error1<KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION) val NO_ELSE_IN_WHEN by error1<KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
val INVALID_IF_AS_EXPRESSION by error0<KtIfExpression>(SourceElementPositioningStrategies.IF_EXPRESSION) val INVALID_IF_AS_EXPRESSION by error0<KtIfExpression>(SourceElementPositioningStrategies.IF_EXPRESSION)
val ELSE_MISPLACED_IN_WHEN by error0<KtWhenEntry>(SourceElementPositioningStrategies.ELSE_ENTRY) val ELSE_MISPLACED_IN_WHEN by error0<KtWhenEntry>(SourceElementPositioningStrategies.ELSE_ENTRY)
@@ -119,6 +119,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EQUALITY_NOT_APPL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_FROM_JAVA_RESOLUTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_FROM_JAVA_RESOLUTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_IN_CONTRACT_DESCRIPTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_IN_CONTRACT_DESCRIPTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPANDED_TYPE_CANNOT_BE_INHERITED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPANDED_TYPE_CANNOT_BE_INHERITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_CONDITION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DECLARATION_WITH_BODY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DECLARATION_WITH_BODY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DELEGATED_PROPERTY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_DELEGATED_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_LATEINIT_PROPERTY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_LATEINIT_PROPERTY
@@ -1152,6 +1153,7 @@ class FirDefaultErrorMessages {
map.put(USELESS_IS_CHECK, "Check for instance is always ''{0}''", TO_STRING) map.put(USELESS_IS_CHECK, "Check for instance is always ''{0}''", TO_STRING)
// When expressions // When expressions
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(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(INVALID_IF_AS_EXPRESSION, "'if' must have both main and 'else' branches if used as an expression")
@@ -75,7 +75,7 @@ private fun ConeDiagnostic.toFirDiagnostic(
FirErrors.NO_TYPE_ARGUMENTS_ON_RHS.createOn(qualifiedAccessSource ?: source, this.desiredCount, this.type) FirErrors.NO_TYPE_ARGUMENTS_ON_RHS.createOn(qualifiedAccessSource ?: source, this.desiredCount, this.type)
is ConeSimpleDiagnostic -> when (source.kind) { is ConeSimpleDiagnostic -> when (source.kind) {
is FirFakeSourceElementKind -> null is FirFakeSourceElementKind -> null
else -> this.getFactory(source).createOn(qualifiedAccessSource ?: source) else -> this.getFactory(source)?.createOn(qualifiedAccessSource ?: source)
} }
is ConeInstanceAccessBeforeSuperCall -> FirErrors.INSTANCE_ACCESS_BEFORE_SUPER_CALL.createOn(source, this.target) is ConeInstanceAccessBeforeSuperCall -> FirErrors.INSTANCE_ACCESS_BEFORE_SUPER_CALL.createOn(source, this.target)
is ConeStubDiagnostic -> null is ConeStubDiagnostic -> null
@@ -322,7 +322,7 @@ private fun ConstraintSystemError.toDiagnostic(
private val NewConstraintError.lowerConeType: ConeKotlinType get() = lowerType as ConeKotlinType private val NewConstraintError.lowerConeType: ConeKotlinType get() = lowerType as ConeKotlinType
private val NewConstraintError.upperConeType: ConeKotlinType get() = upperType as ConeKotlinType private val NewConstraintError.upperConeType: ConeKotlinType get() = upperType as ConeKotlinType
private fun ConeSimpleDiagnostic.getFactory(source: FirSourceElement): FirDiagnosticFactory0 { private fun ConeSimpleDiagnostic.getFactory(source: FirSourceElement): FirDiagnosticFactory0? {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return when (kind) { return when (kind) {
DiagnosticKind.Syntax -> FirErrors.SYNTAX DiagnosticKind.Syntax -> FirErrors.SYNTAX
@@ -341,6 +341,7 @@ private fun ConeSimpleDiagnostic.getFactory(source: FirSourceElement): FirDiagno
DiagnosticKind.ExpressionExpected -> when (source.elementType) { DiagnosticKind.ExpressionExpected -> when (source.elementType) {
KtNodeTypes.BINARY_EXPRESSION -> FirErrors.ASSIGNMENT_IN_EXPRESSION_CONTEXT KtNodeTypes.BINARY_EXPRESSION -> FirErrors.ASSIGNMENT_IN_EXPRESSION_CONTEXT
KtNodeTypes.FUN -> FirErrors.ANONYMOUS_FUNCTION_WITH_NAME KtNodeTypes.FUN -> FirErrors.ANONYMOUS_FUNCTION_WITH_NAME
KtNodeTypes.WHEN_CONDITION_IN_RANGE, KtNodeTypes.WHEN_CONDITION_IS_PATTERN -> FirErrors.EXPECTED_CONDITION
else -> FirErrors.EXPRESSION_EXPECTED else -> FirErrors.EXPRESSION_EXPECTED
} }
DiagnosticKind.JumpOutsideLoop -> FirErrors.BREAK_OR_CONTINUE_OUTSIDE_A_LOOP DiagnosticKind.JumpOutsideLoop -> FirErrors.BREAK_OR_CONTINUE_OUTSIDE_A_LOOP
@@ -364,6 +365,7 @@ private fun ConeSimpleDiagnostic.getFactory(source: FirSourceElement): FirDiagno
DiagnosticKind.UnresolvedSupertype, DiagnosticKind.UnresolvedSupertype,
DiagnosticKind.UnresolvedExpandedType, DiagnosticKind.UnresolvedExpandedType,
DiagnosticKind.Other -> FirErrors.OTHER_ERROR DiagnosticKind.Other -> FirErrors.OTHER_ERROR
DiagnosticKind.NotRootCause -> null
else -> throw IllegalArgumentException("Unsupported diagnostic kind: $kind at $javaClass") else -> throw IllegalArgumentException("Unsupported diagnostic kind: $kind at $javaClass")
} }
} }
@@ -704,7 +704,13 @@ class ExpressionsConverter(
@OptIn(FirContractViolation::class) @OptIn(FirContractViolation::class)
val subject = FirExpressionRef<FirWhenExpression>() val subject = FirExpressionRef<FirWhenExpression>()
whenEntryNodes.mapTo(whenEntries) { convertWhenEntry(it, subject.takeIf { hasSubject }) } whenEntryNodes.mapTo(whenEntries) {
convertWhenEntry(
it,
subject.takeIf { hasSubject },
hasSubjectVariable = subjectVariable != null
)
}
return buildWhenExpression { return buildWhenExpression {
source = whenExpression.toFirSourceElement() source = whenExpression.toFirSourceElement()
this.subject = subjectExpression this.subject = subjectExpression
@@ -748,15 +754,19 @@ class ExpressionsConverter(
* @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseWhenEntry * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseWhenEntry
* @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseWhenEntryNotElse * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseWhenEntryNotElse
*/ */
private fun convertWhenEntry(whenEntry: LighterASTNode, whenRefWithSubject: FirExpressionRef<FirWhenExpression>?): WhenEntry { private fun convertWhenEntry(
whenEntry: LighterASTNode,
whenRefWithSubject: FirExpressionRef<FirWhenExpression>?,
hasSubjectVariable: Boolean
): WhenEntry {
var isElse = false var isElse = false
var firBlock: FirBlock = buildEmptyExpressionBlock() var firBlock: FirBlock = buildEmptyExpressionBlock()
val conditions = mutableListOf<FirExpression>() val conditions = mutableListOf<FirExpression>()
whenEntry.forEachChildren { whenEntry.forEachChildren {
when (it.tokenType) { when (it.tokenType) {
WHEN_CONDITION_EXPRESSION -> conditions += convertWhenConditionExpression(it, whenRefWithSubject) WHEN_CONDITION_EXPRESSION -> conditions += convertWhenConditionExpression(it, whenRefWithSubject)
WHEN_CONDITION_IN_RANGE -> conditions += convertWhenConditionInRange(it, whenRefWithSubject) WHEN_CONDITION_IN_RANGE -> conditions += convertWhenConditionInRange(it, whenRefWithSubject, hasSubjectVariable)
WHEN_CONDITION_IS_PATTERN -> conditions += convertWhenConditionIsPattern(it, whenRefWithSubject) WHEN_CONDITION_IS_PATTERN -> conditions += convertWhenConditionIsPattern(it, whenRefWithSubject, hasSubjectVariable)
ELSE_KEYWORD -> isElse = true ELSE_KEYWORD -> isElse = true
BLOCK -> firBlock = declarationsConverter.convertBlock(it) BLOCK -> firBlock = declarationsConverter.convertBlock(it)
else -> if (it.isExpression()) firBlock = declarationsConverter.convertBlock(it) else -> if (it.isExpression()) firBlock = declarationsConverter.convertBlock(it)
@@ -799,7 +809,8 @@ class ExpressionsConverter(
private fun convertWhenConditionInRange( private fun convertWhenConditionInRange(
whenCondition: LighterASTNode, whenCondition: LighterASTNode,
whenRefWithSubject: FirExpressionRef<FirWhenExpression>? whenRefWithSubject: FirExpressionRef<FirWhenExpression>?,
hasSubjectVariable: Boolean
): FirExpression { ): FirExpression {
var isNegate = false var isNegate = false
var firExpression: FirExpression? = null var firExpression: FirExpression? = null
@@ -825,7 +836,11 @@ class ExpressionsConverter(
} else { } else {
return buildErrorExpression { return buildErrorExpression {
source = whenCondition.toFirSourceElement() source = whenCondition.toFirSourceElement()
diagnostic = ConeSimpleDiagnostic("No expression in condition with expression", DiagnosticKind.Syntax) diagnostic = ConeSimpleDiagnostic(
"No expression in condition with expression",
// A subject variable without initializer should be highlighted only at the subject expression.
if (hasSubjectVariable) DiagnosticKind.NotRootCause else DiagnosticKind.ExpressionExpected
)
} }
} }
@@ -844,7 +859,8 @@ class ExpressionsConverter(
private fun convertWhenConditionIsPattern( private fun convertWhenConditionIsPattern(
whenCondition: LighterASTNode, whenCondition: LighterASTNode,
whenRefWithSubject: FirExpressionRef<FirWhenExpression>? whenRefWithSubject: FirExpressionRef<FirWhenExpression>?,
hasSubjectVariable: Boolean
): FirExpression { ): FirExpression {
lateinit var firOperation: FirOperation lateinit var firOperation: FirOperation
lateinit var firType: FirTypeRef lateinit var firType: FirTypeRef
@@ -863,7 +879,11 @@ class ExpressionsConverter(
} else { } else {
return buildErrorExpression { return buildErrorExpression {
source = whenCondition.toFirSourceElement() source = whenCondition.toFirSourceElement()
diagnostic = ConeSimpleDiagnostic("No expression in condition with expression", DiagnosticKind.Syntax) diagnostic = ConeSimpleDiagnostic(
"No expression in condition with expression",
// A subject variable without initializer should be highlighted only at the subject expression.
if (hasSubjectVariable) DiagnosticKind.NotRootCause else DiagnosticKind.ExpressionExpected
)
} }
} }
@@ -195,7 +195,7 @@ open class RawFirBuilder(
) )
} }
private fun KtExpression?.toFirExpression( private fun KtElement?.toFirExpression(
errorReason: String, errorReason: String,
kind: DiagnosticKind = DiagnosticKind.ExpressionExpected, kind: DiagnosticKind = DiagnosticKind.ExpressionExpected,
): FirExpression { ): FirExpression {
@@ -1747,10 +1747,17 @@ open class RawFirBuilder(
result = branchBody result = branchBody
} }
} else { } else {
val ktCondition = entry.conditions.first() as? KtWhenConditionWithExpression val ktCondition = entry.conditions.first()
buildWhenBranch { buildWhenBranch {
source = entrySource source = entrySource
condition = ktCondition?.expression.toFirExpression("No expression in condition with expression") condition = ((ktCondition as? KtWhenConditionWithExpression)?.expression
?: ktCondition)
.toFirExpression(
"No expression in condition with expression",
// A subject variable without initializer should be highlighted only at the subject expression.
if (ktSubjectExpression is KtVariableDeclaration) DiagnosticKind.NotRootCause
else DiagnosticKind.ExpressionExpected
)
result = branchBody result = branchBody
} }
} }
@@ -51,5 +51,11 @@ enum class DiagnosticKind {
FloatLiteralOutOfRange, FloatLiteralOutOfRange,
WrongLongSuffix, WrongLongSuffix,
Other Other,
/**
* Special diagnostic kind that is caused by issues at other places in the code. Such diagnostics should not be reported so that user
* sees less noise.
*/
NotRootCause
} }
@@ -9,9 +9,9 @@ fun foo() =
cond2() -> 2 cond2() -> 2
<!CONDITION_TYPE_MISMATCH!>4<!> -> 34 <!CONDITION_TYPE_MISMATCH!>4<!> -> 34
<!CONDITION_TYPE_MISMATCH, TYPE_MISMATCH!>Pair(1, 2)<!> -> 3 <!CONDITION_TYPE_MISMATCH, TYPE_MISMATCH!>Pair(1, 2)<!> -> 3
in 1..10 -> 34 <!EXPECTED_CONDITION!>in 1..10<!> -> 34
<!CONDITION_TYPE_MISMATCH!>4<!> -> 38 <!CONDITION_TYPE_MISMATCH!>4<!> -> 38
is Int -> 33 <!EXPECTED_CONDITION!>is Int<!> -> 33
else -> 34 else -> 34
} }
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression import org.jetbrains.kotlin.psi.KtWhenExpression
@@ -2376,6 +2377,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, token,
) )
} }
add(FirErrors.EXPECTED_CONDITION) { firDiagnostic ->
ExpectedConditionImpl(
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.NO_ELSE_IN_WHEN) { firDiagnostic -> add(FirErrors.NO_ELSE_IN_WHEN) { firDiagnostic ->
NoElseInWhenImpl( NoElseInWhenImpl(
firDiagnostic.a.map { whenMissingCase -> firDiagnostic.a.map { whenMissingCase ->
@@ -55,6 +55,7 @@ import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
@@ -1675,6 +1676,10 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val compileTimeCheckResult: Boolean abstract val compileTimeCheckResult: Boolean
} }
abstract class ExpectedCondition : KtFirDiagnostic<KtWhenCondition>() {
override val diagnosticClass get() = ExpectedCondition::class
}
abstract class NoElseInWhen : KtFirDiagnostic<KtWhenExpression>() { abstract class NoElseInWhen : KtFirDiagnostic<KtWhenExpression>() {
override val diagnosticClass get() = NoElseInWhen::class override val diagnosticClass get() = NoElseInWhen::class
abstract val missingWhenCases: List<WhenMissingCase> abstract val missingWhenCases: List<WhenMissingCase>
@@ -57,6 +57,7 @@ import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
@@ -2708,6 +2709,13 @@ internal class UselessIsCheckImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
} }
internal class ExpectedConditionImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ExpectedCondition(), KtAbstractFirDiagnostic<KtWhenCondition> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class NoElseInWhenImpl( internal class NoElseInWhenImpl(
override val missingWhenCases: List<WhenMissingCase>, override val missingWhenCases: List<WhenMissingCase>,
firDiagnostic: FirPsiDiagnostic, firDiagnostic: FirPsiDiagnostic,