FIR checker: COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT

FIR simply ignores anything after the first comma if the when expression
doesn't have a subject. Hence, the checker has to rely on PSI structure
instead.
This commit is contained in:
Tianyu Geng
2021-07-19 19:27:11 -07:00
committed by Ivan Kochurkin
parent db12a96e54
commit 1679da45ab
16 changed files with 113 additions and 25 deletions
@@ -1018,6 +1018,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error<KtElement> {
parameter<String>("illegalReason")
}
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error<PsiElement>(PositioningStrategy.COMMAS)
}
val CONTEXT_TRACKING by object : DiagnosticGroup("Context tracking") {
@@ -102,6 +102,8 @@ enum class PositioningStrategy(private val strategy: String? = null) {
INLINE_PARAMETER_MODIFIER,
ABSTRACT_MODIFIER,
LABEL,
COMMAS,
;
val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}"
@@ -542,6 +542,7 @@ object FirErrors {
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>()
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error0<PsiElement>(SourceElementPositioningStrategies.COMMAS)
// Context tracking
val TYPE_PARAMETER_IS_NOT_AN_EXPRESSION by error1<KtSimpleNameExpression, FirTypeParameterSymbol>()
@@ -68,6 +68,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirExhaustiveWhenChecker,
FirWhenConditionChecker,
FirWhenSubjectChecker,
FirCommaInWhenConditionChecker,
)
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirExpressionSyntaxChecker
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.getChild
import org.jetbrains.kotlin.fir.expressions.FirWhenBranch
import org.jetbrains.kotlin.fir.expressions.FirWhenExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
import org.jetbrains.kotlin.lexer.KtTokens
object FirCommaInWhenConditionChecker : FirExpressionSyntaxChecker<FirWhenExpression, PsiElement>() {
override fun isApplicable(element: FirWhenExpression, source: FirSourceElement): Boolean {
return element.subject == null
}
override fun checkLightTree(
element: FirWhenExpression,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
for (branch in element.branches) {
if (branch.condition is FirElseIfTrueCondition) continue
checkCommaInBranchCondition(branch, context, reporter)
}
}
private fun checkCommaInBranchCondition(branch: FirWhenBranch, context: CheckerContext, reporter: DiagnosticReporter) {
val source = branch.source
if (source?.elementType == KtNodeTypes.WHEN_ENTRY && source?.getChild(KtTokens.COMMA, depth = 1) != null) {
reporter.reportOn(source, FirErrors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, context)
}
}
}
@@ -7,8 +7,8 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics
import org.jetbrains.kotlin.diagnostics.rendering.LanguageFeatureMessageRenderer
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_POSITION_VARIANCE
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.commaSeparated
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.STRING
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.commaSeparated
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.AMBIGUOUS_CALLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR
@@ -87,6 +87,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CATCH_PARAMETER_W
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_CANNOT_BE_EXTENDED_DIRECTLY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_IN_SUPERTYPE_FOR_ENUM
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_LITERAL_LHS_NOT_A_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_MISSING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_ON_NULLABLE
@@ -1371,6 +1372,7 @@ class FirDefaultErrorMessages {
TO_STRING,
WHEN_MISSING_CASES
)
map.put(COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, "Deprecated syntax. Use '||' instead of commas in when-condition for 'when' without argument")
// Context tracking
map.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", SYMBOL)
@@ -910,6 +910,26 @@ object LightTreePositioningStrategies {
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
}
@OptIn(ExperimentalStdlibApi::class)
val COMMAS: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
override fun mark(
node: LighterASTNode,
startOffset: Int,
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>
): List<TextRange> {
return buildList {
val childrenRef = Ref<Array<LighterASTNode?>>()
tree.getChildren(node, childrenRef)
for (child in childrenRef.get()) {
if (child != null && child.tokenType == KtTokens.COMMA) {
add(markSingleElement(child, child, startOffset, endOffset, tree, node))
}
}
}
}
}
}
fun FirSourceElement.hasValOrVar(): Boolean =
@@ -272,6 +272,10 @@ object SourceElementPositioningStrategies {
LightTreePositioningStrategies.DECLARATION_WITH_BODY,
PositioningStrategies.DECLARATION_WITH_BODY
)
val COMMAS = SourceElementPositioningStrategy(
LightTreePositioningStrategies.COMMAS,
PositioningStrategies.COMMAS
)
val UNREACHABLE_CODE = SourceElementPositioningStrategy(
LightTreePositioningStrategies.UNREACHABLE_CODE,
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.diagnostics
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.cfg.UnreachableCode
@@ -946,6 +945,19 @@ object PositioningStrategies {
}
}
@OptIn(ExperimentalStdlibApi::class)
val COMMAS: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
override fun mark(element: PsiElement): List<TextRange> {
return buildList {
for (child in element.allChildren) {
if (child.node.elementType == KtTokens.COMMA) {
add(markSingleElement(child))
}
}
}
}
}
/**
* @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for
* diagnostics on super and unresolved references. For example, with the following, only the part inside the parentheses should be
@@ -1,7 +1,7 @@
public fun test(o: String?): Boolean {
return when {
// Data flow info should propagate from o == null to o.length
o == null, o.length == 0 -> false
o == null<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> o.length == 0 -> false
else -> true
}
}
}
@@ -1,18 +0,0 @@
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
* SPEC VERSION: 0.1-313
* PRIMARY LINKS: expressions, when-expression -> paragraph 2 -> sentence 1
* expressions, when-expression -> paragraph 5 -> sentence 1
*/
fun foo(x: Int, y: Int): Int =
when {
x > 0, y > 0,<!SYNTAX!>,<!> x < 0 -> 1
else -> 0
}
fun bar(x: Int): Int =
when (x) {
0 -> 0
else -> 1
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
@@ -3,9 +3,9 @@
// TESTCASE NUMBER: 1
fun case_1(value_1: TypesProvider) {
when {
getBoolean(), value_1.getBoolean() -> return
value_1.getBoolean() && getBoolean(), getLong() == 1000L -> return
<!CONDITION_TYPE_MISMATCH, TYPE_MISMATCH!>Out<Int>()<!>, getLong(), {}, Any(), throw Exception() -> return
getBoolean()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> value_1.getBoolean() -> return
value_1.getBoolean() && getBoolean()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> getLong() == 1000L -> return
<!CONDITION_TYPE_MISMATCH, TYPE_MISMATCH!>Out<Int>()<!><!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> getLong()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> {}<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> Any()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> throw Exception() -> return
}
return
@@ -2803,6 +2803,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT) { firDiagnostic ->
CommaInWhenConditionWithoutArgumentImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
TypeParameterIsNotAnExpressionImpl(
firSymbolBuilder.classifierBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir),
@@ -1958,6 +1958,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val illegalReason: String
}
abstract class CommaInWhenConditionWithoutArgument : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = CommaInWhenConditionWithoutArgument::class
}
abstract class TypeParameterIsNotAnExpression : KtFirDiagnostic<KtSimpleNameExpression>() {
override val diagnosticClass get() = TypeParameterIsNotAnExpression::class
abstract val typeParameter: KtTypeParameterSymbol
@@ -3156,6 +3156,13 @@ internal class IllegalDeclarationInWhenSubjectImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class CommaInWhenConditionWithoutArgumentImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.CommaInWhenConditionWithoutArgument(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class TypeParameterIsNotAnExpressionImpl(
override val typeParameter: KtTypeParameterSymbol,
firDiagnostic: FirPsiDiagnostic,