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> {
|
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error<KtElement> {
|
||||||
parameter<String>("illegalReason")
|
parameter<String>("illegalReason")
|
||||||
}
|
}
|
||||||
|
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error<PsiElement>(PositioningStrategy.COMMAS)
|
||||||
}
|
}
|
||||||
|
|
||||||
val CONTEXT_TRACKING by object : DiagnosticGroup("Context tracking") {
|
val CONTEXT_TRACKING by object : DiagnosticGroup("Context tracking") {
|
||||||
|
|||||||
+2
@@ -102,6 +102,8 @@ enum class PositioningStrategy(private val strategy: String? = null) {
|
|||||||
INLINE_PARAMETER_MODIFIER,
|
INLINE_PARAMETER_MODIFIER,
|
||||||
ABSTRACT_MODIFIER,
|
ABSTRACT_MODIFIER,
|
||||||
LABEL,
|
LABEL,
|
||||||
|
COMMAS,
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}"
|
val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}"
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ object FirErrors {
|
|||||||
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)
|
||||||
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error1<KtElement, String>()
|
val ILLEGAL_DECLARATION_IN_WHEN_SUBJECT by error1<KtElement, String>()
|
||||||
|
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error0<PsiElement>(SourceElementPositioningStrategies.COMMAS)
|
||||||
|
|
||||||
// Context tracking
|
// Context tracking
|
||||||
val TYPE_PARAMETER_IS_NOT_AN_EXPRESSION by error1<KtSimpleNameExpression, FirTypeParameterSymbol>()
|
val TYPE_PARAMETER_IS_NOT_AN_EXPRESSION by error1<KtSimpleNameExpression, FirTypeParameterSymbol>()
|
||||||
|
|||||||
+1
@@ -68,6 +68,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
|
|||||||
FirExhaustiveWhenChecker,
|
FirExhaustiveWhenChecker,
|
||||||
FirWhenConditionChecker,
|
FirWhenConditionChecker,
|
||||||
FirWhenSubjectChecker,
|
FirWhenSubjectChecker,
|
||||||
|
FirCommaInWhenConditionChecker,
|
||||||
)
|
)
|
||||||
|
|
||||||
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
|
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.LanguageFeatureMessageRenderer
|
||||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_POSITION_VARIANCE
|
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.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.AMBIGUOUS_CALLS
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR
|
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_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_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.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_AMBIGUITY
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_MISSING
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_MISSING
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_ON_NULLABLE
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_ON_NULLABLE
|
||||||
@@ -1371,6 +1372,7 @@ class FirDefaultErrorMessages {
|
|||||||
TO_STRING,
|
TO_STRING,
|
||||||
WHEN_MISSING_CASES
|
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
|
// Context tracking
|
||||||
map.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", SYMBOL)
|
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)
|
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 =
|
fun FirSourceElement.hasValOrVar(): Boolean =
|
||||||
|
|||||||
+4
@@ -272,6 +272,10 @@ object SourceElementPositioningStrategies {
|
|||||||
LightTreePositioningStrategies.DECLARATION_WITH_BODY,
|
LightTreePositioningStrategies.DECLARATION_WITH_BODY,
|
||||||
PositioningStrategies.DECLARATION_WITH_BODY
|
PositioningStrategies.DECLARATION_WITH_BODY
|
||||||
)
|
)
|
||||||
|
val COMMAS = SourceElementPositioningStrategy(
|
||||||
|
LightTreePositioningStrategies.COMMAS,
|
||||||
|
PositioningStrategies.COMMAS
|
||||||
|
)
|
||||||
|
|
||||||
val UNREACHABLE_CODE = SourceElementPositioningStrategy(
|
val UNREACHABLE_CODE = SourceElementPositioningStrategy(
|
||||||
LightTreePositioningStrategies.UNREACHABLE_CODE,
|
LightTreePositioningStrategies.UNREACHABLE_CODE,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.diagnostics
|
|||||||
|
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
|
||||||
import com.intellij.psi.tree.TokenSet
|
import com.intellij.psi.tree.TokenSet
|
||||||
import org.jetbrains.kotlin.KtNodeTypes
|
import org.jetbrains.kotlin.KtNodeTypes
|
||||||
import org.jetbrains.kotlin.cfg.UnreachableCode
|
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
|
* @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
|
* 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 {
|
public fun test(o: String?): Boolean {
|
||||||
return when {
|
return when {
|
||||||
// Data flow info should propagate from o == null to o.length
|
// 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
|
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)
|
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
|
||||||
*
|
*
|
||||||
|
|||||||
Vendored
+3
-3
@@ -3,9 +3,9 @@
|
|||||||
// TESTCASE NUMBER: 1
|
// TESTCASE NUMBER: 1
|
||||||
fun case_1(value_1: TypesProvider) {
|
fun case_1(value_1: TypesProvider) {
|
||||||
when {
|
when {
|
||||||
getBoolean(), value_1.getBoolean() -> return
|
getBoolean()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> value_1.getBoolean() -> return
|
||||||
value_1.getBoolean() && getBoolean(), getLong() == 1000L -> return
|
value_1.getBoolean() && getBoolean()<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> getLong() == 1000L -> return
|
||||||
<!CONDITION_TYPE_MISMATCH, TYPE_MISMATCH!>Out<Int>()<!>, getLong(), {}, Any(), throw Exception() -> 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
|
return
|
||||||
|
|||||||
+6
@@ -2803,6 +2803,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
|||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(FirErrors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT) { firDiagnostic ->
|
||||||
|
CommaInWhenConditionWithoutArgumentImpl(
|
||||||
|
firDiagnostic as FirPsiDiagnostic,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
add(FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
|
add(FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
|
||||||
TypeParameterIsNotAnExpressionImpl(
|
TypeParameterIsNotAnExpressionImpl(
|
||||||
firSymbolBuilder.classifierBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir),
|
firSymbolBuilder.classifierBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir),
|
||||||
|
|||||||
+4
@@ -1958,6 +1958,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
|||||||
abstract val illegalReason: String
|
abstract val illegalReason: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class CommaInWhenConditionWithoutArgument : KtFirDiagnostic<PsiElement>() {
|
||||||
|
override val diagnosticClass get() = CommaInWhenConditionWithoutArgument::class
|
||||||
|
}
|
||||||
|
|
||||||
abstract class TypeParameterIsNotAnExpression : KtFirDiagnostic<KtSimpleNameExpression>() {
|
abstract class TypeParameterIsNotAnExpression : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||||
override val diagnosticClass get() = TypeParameterIsNotAnExpression::class
|
override val diagnosticClass get() = TypeParameterIsNotAnExpression::class
|
||||||
abstract val typeParameter: KtTypeParameterSymbol
|
abstract val typeParameter: KtTypeParameterSymbol
|
||||||
|
|||||||
+7
@@ -3156,6 +3156,13 @@ internal class IllegalDeclarationInWhenSubjectImpl(
|
|||||||
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
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(
|
internal class TypeParameterIsNotAnExpressionImpl(
|
||||||
override val typeParameter: KtTypeParameterSymbol,
|
override val typeParameter: KtTypeParameterSymbol,
|
||||||
firDiagnostic: FirPsiDiagnostic,
|
firDiagnostic: FirPsiDiagnostic,
|
||||||
|
|||||||
Reference in New Issue
Block a user