[FIR] Prohibit confusing syntax inside when branches

^KT-48385
This commit is contained in:
Dmitriy Novozhilov
2021-10-21 14:50:14 +03:00
committed by teamcityserver
parent bf453674b9
commit 94664694df
22 changed files with 280 additions and 262 deletions
@@ -5,64 +5,13 @@
package org.jetbrains.kotlin.analysis.api.fir.diagnostics
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.FirVariable
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtAnonymousInitializer
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtBackingField
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
/*
* This file was generated automatically
@@ -3157,6 +3106,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.CONFUSING_BRANCH_CONDITION.errorFactory) { firDiagnostic ->
ConfusingBranchConditionErrorImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.CONFUSING_BRANCH_CONDITION.warningFactory) { firDiagnostic ->
ConfusingBranchConditionWarningImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
TypeParameterIsNotAnExpressionImpl(
firSymbolBuilder.classifierBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir),
@@ -8,13 +8,7 @@ package org.jetbrains.kotlin.analysis.api.fir.diagnostics
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.analysis.api.diagnostics.KtDiagnosticWithPsi
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtVariableLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtVariableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
@@ -30,49 +24,7 @@ import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtAnonymousInitializer
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtBackingField
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
@@ -2203,6 +2155,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = DuplicateLabelInWhen::class
}
abstract class ConfusingBranchConditionError : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConfusingBranchConditionError::class
}
abstract class ConfusingBranchConditionWarning : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConfusingBranchConditionWarning::class
}
abstract class TypeParameterIsNotAnExpression : KtFirDiagnostic<KtSimpleNameExpression>() {
override val diagnosticClass get() = TypeParameterIsNotAnExpression::class
abstract val typeParameter: KtTypeParameterSymbol
@@ -7,13 +7,7 @@ package org.jetbrains.kotlin.analysis.api.fir.diagnostics
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtVariableLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtVariableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.tokens.ValidityToken
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.config.LanguageFeature
@@ -31,49 +25,7 @@ import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtAnonymousInitializer
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtBackingField
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeParameter
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.psi.KtWhenCondition
import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
@@ -2655,6 +2607,16 @@ internal class DuplicateLabelInWhenImpl(
override val token: ValidityToken,
) : KtFirDiagnostic.DuplicateLabelInWhen(), KtAbstractFirDiagnostic<KtElement>
internal class ConfusingBranchConditionErrorImpl(
override val firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ConfusingBranchConditionError(), KtAbstractFirDiagnostic<PsiElement>
internal class ConfusingBranchConditionWarningImpl(
override val firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ConfusingBranchConditionWarning(), KtAbstractFirDiagnostic<PsiElement>
internal class TypeParameterIsNotAnExpressionImpl(
override val typeParameter: KtTypeParameterSymbol,
override val firDiagnostic: FirPsiDiagnostic,
@@ -1117,6 +1117,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT by error<PsiElement>(PositioningStrategy.COMMAS)
val DUPLICATE_LABEL_IN_WHEN by warning<KtElement>()
val CONFUSING_BRANCH_CONDITION by deprecationError<PsiElement>(LanguageFeature.ProhibitConfusingSyntaxInWhenBranches)
}
val CONTEXT_TRACKING by object : DiagnosticGroup("Context tracking") {
@@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitAssigningSingleElementsToVarargsInNamedForm
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitConfusingSyntaxInWhenBranches
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitInvisibleAbstractMethodsInSuperclasses
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitNonReifiedArraysAsReifiedTypeArguments
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitUseSiteTargetAnnotationsOnSuperTypes
@@ -596,6 +597,7 @@ object FirErrors {
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>()
val CONFUSING_BRANCH_CONDITION by deprecationError0<PsiElement>(ProhibitConfusingSyntaxInWhenBranches)
// Context tracking
val TYPE_PARAMETER_IS_NOT_AN_EXPRESSION by error1<KtSimpleNameExpression, FirTypeParameterSymbol>()
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirCommaInWhenConditionChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirConfusingWhenBranchSyntaxChecker
object CommonExpressionCheckers : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
@@ -77,6 +78,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirWhenConditionChecker,
FirWhenSubjectChecker,
FirCommaInWhenConditionChecker,
FirConfusingWhenBranchSyntaxChecker,
)
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
@@ -0,0 +1,118 @@
/*
* 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.syntax
import com.intellij.lang.LighterASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.ElementTypeUtils.getOperationSymbol
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirRealPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getChildren
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.isExpression
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.expressions.FirWhenExpression
import org.jetbrains.kotlin.lexer.KtTokens.*
import org.jetbrains.kotlin.psi.*
object FirConfusingWhenBranchSyntaxChecker : FirExpressionSyntaxChecker<FirWhenExpression, PsiElement>() {
private val prohibitedTokens = TokenSet.create(
IN_KEYWORD, NOT_IN,
LT, LTEQ, GT, GTEQ,
EQEQ, EXCLEQ, EQEQEQ, EXCLEQEQEQ,
ANDAND, OROR
)
override fun checkLightTree(
element: FirWhenExpression,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (element.subject == null && element.subjectVariable == null) return
val tree = source.treeStructure
val entries = source.lighterASTNode.getChildren(tree).filter { it.tokenType == WHEN_ENTRY }
val offset = source.startOffset - source.lighterASTNode.startOffset
for (entry in entries) {
for (node in entry.getChildren(tree)) {
val expression = when (node.tokenType) {
WHEN_CONDITION_EXPRESSION -> node.getChildren(tree).firstOrNull { it.isExpression() }
WHEN_CONDITION_IN_RANGE -> node.getChildren(tree)
.firstOrNull { it.tokenType != OPERATION_REFERENCE && it.isExpression()}
else -> null
} ?: continue
checkConditionExpression(offset, expression, tree, context, reporter)
}
}
}
private fun checkConditionExpression(
offset: Int,
expression: LighterASTNode,
tree: FlyweightCapableTreeStructure<LighterASTNode>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val shouldReport = when (expression.tokenType) {
IS_EXPRESSION -> true
BINARY_EXPRESSION -> {
val operationTokenName = expression.getChildren(tree).first { it.tokenType == OPERATION_REFERENCE }.toString()
val operationToken = operationTokenName.getOperationSymbol()
operationToken in prohibitedTokens
}
else -> false
}
if (shouldReport) {
val source = FirLightSourceElement(expression, offset + expression.startOffset, offset + expression.endOffset, tree)
reporter.reportOn(source, FirErrors.CONFUSING_BRANCH_CONDITION, context)
}
}
override fun checkPsi(
element: FirWhenExpression,
source: FirPsiSourceElement,
psi: PsiElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (element.subject == null && element.subjectVariable == null) return
val whenExpression = psi as KtWhenExpression
if (whenExpression.subjectExpression == null && whenExpression.subjectVariable == null) return
for (entry in whenExpression.entries) {
for (condition in entry.conditions) {
checkCondition(condition, context, reporter)
}
}
}
private fun checkCondition(condition: KtWhenCondition, context: CheckerContext, reporter: DiagnosticReporter) {
when (condition) {
is KtWhenConditionWithExpression -> checkConditionExpression(condition.expression, context, reporter)
is KtWhenConditionInRange -> checkConditionExpression(condition.rangeExpression, context, reporter)
}
}
private fun checkConditionExpression(rawExpression: KtExpression?, context: CheckerContext, reporter: DiagnosticReporter) {
if (rawExpression == null) return
if (rawExpression is KtParenthesizedExpression) return
val shouldReport = when (val expression = KtPsiUtil.safeDeparenthesize(rawExpression)) {
is KtIsExpression -> true
is KtBinaryExpression -> expression.operationToken in prohibitedTokens
else -> false
}
if (shouldReport) {
val source = FirRealPsiSourceElement(rawExpression)
reporter.reportOn(source, FirErrors.CONFUSING_BRANCH_CONDITION, context)
}
}
}
@@ -107,6 +107,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_OVERL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION_IN_TYPEALIAS_EXPANSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_UPPER_BOUNDS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFUSING_BRANCH_CONDITION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONSTRUCTOR_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONSTRUCTOR_IN_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT
@@ -1520,6 +1521,10 @@ class FirDefaultErrorMessages {
)
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")
map.put(
CONFUSING_BRANCH_CONDITION,
"The logical expressions may be understood ambiguously in when with subject branches. Please wrap it with parenthesis"
)
// Context tracking
map.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", SYMBOL)
@@ -10,6 +10,7 @@ import com.intellij.openapi.util.Ref
import com.intellij.psi.TokenType
import com.intellij.psi.tree.IElementType
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.ElementTypeUtils.isExpression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.builder.BaseFirBuilder
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.fir.lightTree.converter
import com.intellij.lang.LighterASTNode
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.KtNodeType
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
@@ -20,25 +18,16 @@ import org.jetbrains.kotlin.fir.declarations.builder.buildProperty
import org.jetbrains.kotlin.fir.declarations.impl.FirDeclarationStatusImpl
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.builder.*
import org.jetbrains.kotlin.fir.expressions.builder.FirCallBuilder
import org.jetbrains.kotlin.fir.expressions.builder.buildArgumentList
import org.jetbrains.kotlin.fir.expressions.builder.buildBlock
import org.jetbrains.kotlin.fir.expressions.builder.buildComponentCall
import org.jetbrains.kotlin.fir.fakeElement
import org.jetbrains.kotlin.fir.lightTree.fir.DestructuringDeclaration
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.parsing.KotlinExpressionParsing
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.stubs.elements.KtConstantExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtStringTemplateExpressionElementType
private val expressionSet = listOf(
REFERENCE_EXPRESSION,
DOT_QUALIFIED_EXPRESSION,
LAMBDA_EXPRESSION,
FUN
)
fun String?.nameAsSafeName(defaultName: String = ""): Name {
return when {
@@ -48,28 +37,10 @@ fun String?.nameAsSafeName(defaultName: String = ""): Name {
}
}
fun String.getOperationSymbol(): IElementType {
KotlinExpressionParsing.ALL_OPERATIONS.types.forEach {
if (it is KtSingleValueToken && it.value == this) return it
}
if (this == "as?") return KtTokens.AS_SAFE
return KtTokens.IDENTIFIER
}
fun LighterASTNode.getAsStringWithoutBacktick(): String {
return this.toString().replace("`", "")
}
fun LighterASTNode.isExpression(): Boolean {
return when (this.tokenType) {
is KtNodeType,
is KtConstantExpressionElementType,
is KtStringTemplateExpressionElementType,
in expressionSet -> true
else -> false
}
}
fun <T : FirCallBuilder> T.extractArgumentsFrom(container: List<FirExpression>): T {
argumentList = buildArgumentList {
arguments += container
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.lightTree.converter
import com.intellij.lang.LighterASTNode
import com.intellij.psi.TokenType
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.ElementTypeUtils.isExpression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.builtins.StandardNames
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.fir.lightTree.converter
import com.intellij.lang.LighterASTNode
import com.intellij.psi.TokenType
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.ElementTypeUtils.getOperationSymbol
import org.jetbrains.kotlin.ElementTypeUtils.isExpression
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
@@ -837,7 +839,7 @@ class ExpressionsConverter(
it.tokenType == OPERATION_REFERENCE -> {
conditionSource = it.toFirSourceElement()
}
else -> if (it.isExpression()) firExpression = getAsFirExpression(it)
else -> if (it.isExpression()) firExpression = getAsFirExpression(it, "No range in condition with range")
}
}
@@ -5,9 +5,16 @@
package org.jetbrains.kotlin
import com.intellij.lang.LighterASTNode
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.IErrorCounterReparseableElementType
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.parsing.KotlinExpressionParsing
import org.jetbrains.kotlin.psi.stubs.elements.KtConstantExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtStringTemplateExpressionElementType
object ElementTypeUtils {
@JvmStatic
@@ -32,4 +39,29 @@ object ElementTypeUtils {
}
return balance
}
}
fun String.getOperationSymbol(): IElementType {
KotlinExpressionParsing.ALL_OPERATIONS.types.forEach {
if (it is KtSingleValueToken && it.value == this) return it
}
if (this == "as?") return KtTokens.AS_SAFE
return KtTokens.IDENTIFIER
}
private val expressionSet = listOf(
REFERENCE_EXPRESSION,
DOT_QUALIFIED_EXPRESSION,
LAMBDA_EXPRESSION,
FUN
)
fun LighterASTNode.isExpression(): Boolean {
return when (this.tokenType) {
is KtNodeType,
is KtConstantExpressionElementType,
is KtStringTemplateExpressionElementType,
in expressionSet -> true
else -> false
}
}
}
@@ -55,18 +55,18 @@ fun testWithSubject_ok(x: A, y: A?, any: Any, z: B) {
fun testWithSubject_bad_1(x: A) {
// bad
when (x) {
x in x -> {}
x !in x -> {}
x is String -> {}
x !is String -> {}
x < x -> {}
x > x -> {}
x <= x -> {}
x >= x -> {}
x == x -> {}
x != x -> {}
x === x -> {}
x !== x -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x in x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x !in x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x is String<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x !is String<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x < x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x > x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x <= x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x >= x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x == x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x != x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x === x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>x !== x<!> -> {}
}
// ok
when (x) {
@@ -88,8 +88,8 @@ fun testWithSubject_bad_1(x: A) {
fun testWithSubject_bad_2(b: Boolean) {
// bad
when (b) {
b && b -> {}
b || b -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>b && b<!> -> {}
<!CONFUSING_BRANCH_CONDITION_ERROR!>b || b<!> -> {}
}
// ok
when (b) {
@@ -159,18 +159,18 @@ fun testWithRange_ok(x: A, y: A?, any: Any, z: B) {
fun testWithRange_bad_1(x: A) {
// bad
when (x) {
in x in x -> {}
in x !in x -> {}
in x is String -> {}
in x !is String -> {}
in x < x -> {}
in x > x -> {}
in x <= x -> {}
in x >= x -> {}
in x == x -> {}
in x != x -> {}
in x === x -> {}
in x !== x -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x in x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x !in x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x is String<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x !is String<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x < x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x > x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x <= x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x >= x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x == x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x != x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x === x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>x !== x<!> -> {}
}
// ok
when (x) {
@@ -192,8 +192,8 @@ fun testWithRange_bad_1(x: A) {
fun testWithRange_bad_2(b: Boolean) {
// bad
when (b) {
in b && b -> {}
in b || b -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>b && b<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_ERROR!>b || b<!> -> {}
}
// ok
when (b) {
@@ -55,18 +55,18 @@ fun testWithSubject_ok(x: A, y: A?, any: Any, z: B) {
fun testWithSubject_bad_1(x: A) {
// bad
when (x) {
x in x -> {}
x !in x -> {}
x is String -> {}
x !is String -> {}
x < x -> {}
x > x -> {}
x <= x -> {}
x >= x -> {}
x == x -> {}
x != x -> {}
x === x -> {}
x !== x -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x in x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x !in x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x is String<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x !is String<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x < x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x > x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x <= x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x >= x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x == x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x != x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x === x<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>x !== x<!> -> {}
}
// ok
when (x) {
@@ -88,8 +88,8 @@ fun testWithSubject_bad_1(x: A) {
fun testWithSubject_bad_2(b: Boolean) {
// bad
when (b) {
b && b -> {}
b || b -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>b && b<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>b || b<!> -> {}
}
// ok
when (b) {
@@ -159,18 +159,18 @@ fun testWithRange_ok(x: A, y: A?, any: Any, z: B) {
fun testWithRange_bad_1(x: A) {
// bad
when (x) {
in x in x -> {}
in x !in x -> {}
in x is String -> {}
in x !is String -> {}
in x < x -> {}
in x > x -> {}
in x <= x -> {}
in x >= x -> {}
in x == x -> {}
in x != x -> {}
in x === x -> {}
in x !== x -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x in x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x !in x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x is String<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x !is String<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x < x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x > x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x <= x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x >= x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x == x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x != x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x === x<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>x !== x<!> -> {}
}
// ok
when (x) {
@@ -192,8 +192,8 @@ fun testWithRange_bad_1(x: A) {
fun testWithRange_bad_2(b: Boolean) {
// bad
when (b) {
in b && b -> {}
in b || b -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>b && b<!> -> {}
in <!CONFUSING_BRANCH_CONDITION_WARNING!>b || b<!> -> {}
}
// ok
when (b) {
@@ -29,7 +29,7 @@ fun case_3(value_1: Boolean?): Int = <!NO_ELSE_IN_WHEN!>when<!>(value_1) { }
// TESTCASE NUMBER: 4
fun case_4(value_1: Boolean?): String = <!NO_ELSE_IN_WHEN!>when<!> (value_1) {
true && false && ((true || false)) || true && !!!false && !!!true -> ""
true && false && ((true || false)) || true && !!!false -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false && !!!true<!> -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false<!> -> ""
null -> ""
}
@@ -48,6 +48,6 @@ fun case_5(value_1: Boolean): String {
// TESTCASE NUMBER: 6
fun case_6(value_1: Boolean): String = <!NO_ELSE_IN_WHEN!>when<!> (value_1) {
true && false && ((true || false)) || true && !!!false && !!!true -> ""
true && false && ((true || false)) || true && !!!false -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false && !!!true<!> -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false<!> -> ""
}
@@ -18,7 +18,7 @@ fun case_1(value_1: Boolean?): String = when (value_1) {
// TESTCASE NUMBER: 2
fun case_2(value_1: Boolean?): String = <!NO_ELSE_IN_WHEN!>when<!> (value_1) {
true && false && ((true || false)) || true && !!!false && !!!true -> ""
true && false && ((true || false)) || true && !!!false -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false && !!!true<!> -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false<!> -> ""
null -> ""
}
@@ -17,6 +17,6 @@ fun case_1(value_1: Boolean): String = when (value_1) {
// TESTCASE NUMBER: 2
fun case_2(value_1: Boolean): String = <!NO_ELSE_IN_WHEN!>when<!> (value_1) {
true && false && ((true || false)) || true && !!!false && !!!true -> ""
true && false && ((true || false)) || true && !!!false -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false && !!!true<!> -> ""
<!CONFUSING_BRANCH_CONDITION_WARNING!>true && false && ((true || false)) || true && !!!false<!> -> ""
}
@@ -47,13 +47,13 @@ fun case_4(value_1: Int, value_2: String, value_3: String) {
fun case_5(value_1: Int, value_2: Int, value_3: Boolean?) {
when (value_1) {
1 -> when (value_3) {
value_2 > 1000 -> "1"
value_2 > 100 -> "2"
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 1000<!> -> "1"
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 100<!> -> "2"
else -> "3"
}
2 -> <!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (value_3) {
value_2 > 1000 -> "1"
value_2 > 100 -> "2"
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 1000<!> -> "1"
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 100<!> -> "2"
}
3 -> <!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (value_3) {}
4 -> when (value_3) {
@@ -72,8 +72,8 @@ fun case_5(value_1: Int, value_2: Int, value_3: Boolean?) {
// TESTCASE NUMBER: 6
fun case_6(value_1: Int, value_2: Int, value_3: Boolean?) = when (value_1) {
1 -> when (value_3) {
value_2 > 1000 -> 1
value_2 > 100 -> 2
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 1000<!> -> 1
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_2 > 100<!> -> 2
else -> 3
}
else -> when (value_3) {
@@ -27,10 +27,10 @@ fun case_3(value_1: Boolean, value_2: Boolean, value_3: Long) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (value_1) {
value_2 -> {}
!value_2 -> {}
getBoolean() && value_2 -> {}
getChar() != 'a' -> {}
getList() === getAny() -> {}
value_3 <= 11 -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>getBoolean() && value_2<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>getChar() != 'a'<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>getList() === getAny()<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>value_3 <= 11<!> -> {}
}
}
@@ -19,8 +19,8 @@ fun case_2(value_1: Number, value_2: Int) {
// TESTCASE NUMBER: 3
fun case_3(value_1: Boolean, value_2: Boolean, value_3: Long) {
<!NON_EXHAUSTIVE_WHEN_STATEMENT!>when<!> (value_1) {
value_2, !value_2, getBoolean() && value_2, getChar() != 'a' -> {}
getList() === getAny(), value_3 <= 11 -> {}
value_2, !value_2, <!CONFUSING_BRANCH_CONDITION_WARNING!>getBoolean() && value_2<!>, <!CONFUSING_BRANCH_CONDITION_WARNING!>getChar() != 'a'<!> -> {}
<!CONFUSING_BRANCH_CONDITION_WARNING!>getList() === getAny()<!>, <!CONFUSING_BRANCH_CONDITION_WARNING!>value_3 <= 11<!> -> {}
}
}