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:
committed by
Ivan Kochurkin
parent
db12a96e54
commit
1679da45ab
+1
@@ -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") {
|
||||
|
||||
+2
@@ -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>()
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
|
||||
FirExhaustiveWhenChecker,
|
||||
FirWhenConditionChecker,
|
||||
FirWhenSubjectChecker,
|
||||
FirCommaInWhenConditionChecker,
|
||||
)
|
||||
|
||||
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
|
||||
|
||||
+45
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+20
@@ -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 =
|
||||
|
||||
+4
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-18
@@ -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
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
/*
|
||||
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
|
||||
*
|
||||
|
||||
Vendored
+3
-3
@@ -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
|
||||
|
||||
+6
@@ -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),
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+7
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user