diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt index 8d33811f85a..3d9c85f8769 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt @@ -9,9 +9,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineBodyQuali import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineBodyResolvedQualifierChecker import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineBodyVariableAssignmentChecker 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 -import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirUnderscoredTypeArgumentSyntaxChecker +import org.jetbrains.kotlin.fir.analysis.checkers.syntax.* object CommonExpressionCheckers : ExpressionCheckers() { override val annotationCallCheckers: Set @@ -27,6 +25,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { FirDeprecationChecker, FirRecursiveProblemChecker, FirOptInUsageAccessChecker, + FirPrefixAndSuffixSyntaxChecker, ) override val throwExpressionCheckers: Set diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirPrefixAndSuffixSyntaxChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirPrefixAndSuffixSyntaxChecker.kt new file mode 100644 index 00000000000..0c919413e8d --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirPrefixAndSuffixSyntaxChecker.kt @@ -0,0 +1,124 @@ +/* + * 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.IElementType +import com.intellij.util.diff.FlyweightCapableTreeStructure +import org.jetbrains.kotlin.* +import org.jetbrains.kotlin.KtNodeTypes.BINARY_EXPRESSION +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.psiUtil.nextLeaf +import org.jetbrains.kotlin.psi.psiUtil.prevLeaf +import org.jetbrains.kotlin.util.getChildren + +object FirPrefixAndSuffixSyntaxChecker : FirExpressionSyntaxChecker() { + + private val literalConstants = listOf(KtNodeTypes.CHARACTER_CONSTANT, KtNodeTypes.FLOAT_CONSTANT, KtNodeTypes.INTEGER_CONSTANT) + + override fun isApplicable(element: FirStatement, source: KtSourceElement): Boolean = + source.kind !is KtFakeSourceElementKind && (source.elementType == KtNodeTypes.STRING_TEMPLATE || source.elementType in literalConstants) + + override fun checkPsi( + element: FirStatement, + source: KtPsiSourceElement, + psi: KtExpression, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + psi.prevLeaf()?.let { checkLiteralPrefixOrSuffix(it, context, reporter) } + psi.nextLeaf()?.let { checkLiteralPrefixOrSuffix(it, context, reporter) } + } + + + override fun checkLightTree( + element: FirStatement, + source: KtLightSourceElement, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + source.lighterASTNode.prevLeaf(source.treeStructure) + ?.let { checkLiteralPrefixOrSuffix(it, source, context, reporter) } + source.lighterASTNode.nextLeaf(source.treeStructure) + ?.let { checkLiteralPrefixOrSuffix(it, source, context, reporter) } + } + + private enum class Direction(val offset: Int) { + PREVIOUS(-1), + NEXT(1) + } + + private fun LighterASTNode.getLeaf( + direction: Direction, + treeStructure: FlyweightCapableTreeStructure, + ): LighterASTNode? { + val parent = treeStructure.getParent(this) ?: return null + val children = parent.getChildren(treeStructure) + val index = children.indexOf(this) + val leaf = children.getOrNull(index - direction.offset) + return when { + // Necessary for finding the next leaf in complex binary expressions, for example 'a foo"asdsfsa"foo a' + leaf == null && parent.tokenType == BINARY_EXPRESSION -> parent.getLeaf(direction, treeStructure) + leaf == null -> return null + else -> { + // This is necessary to obtain the simplest node, as the found leaf can be a complex expression + var result = leaf + var resultChildren = leaf.getChildren(treeStructure) + while (resultChildren.isNotEmpty()) { + result = if (direction == Direction.PREVIOUS) resultChildren.first() else resultChildren.last() + resultChildren = result.getChildren(treeStructure) + } + result + } + } + } + + private fun LighterASTNode.prevLeaf(treeStructure: FlyweightCapableTreeStructure): LighterASTNode? { + return getLeaf(Direction.PREVIOUS, treeStructure) + } + + private fun LighterASTNode.nextLeaf(treeStructure: FlyweightCapableTreeStructure): LighterASTNode? { + return getLeaf(Direction.NEXT, treeStructure) + } + + private fun checkLiteralPrefixOrSuffix( + prefixOrSuffix: PsiElement, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + if (illegalLiteralPrefixOrSuffix(prefixOrSuffix.node.elementType)) { + report(prefixOrSuffix.toKtPsiSourceElement(), context, reporter) + } + } + + private fun checkLiteralPrefixOrSuffix( + prefixOrSuffix: LighterASTNode, + source: KtSourceElement, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + val elementType = prefixOrSuffix.tokenType ?: return + if (illegalLiteralPrefixOrSuffix(elementType)) { + report(prefixOrSuffix.toKtLightSourceElement(source.treeStructure), context, reporter) + } + } + + private fun illegalLiteralPrefixOrSuffix(elementType: IElementType): Boolean = + (elementType === KtTokens.IDENTIFIER || elementType === KtTokens.INTEGER_LITERAL || elementType === KtTokens.FLOAT_LITERAL || elementType is KtKeywordToken) + + + private fun report(source: KtSourceElement, context: CheckerContext, reporter: DiagnosticReporter) { + reporter.reportOn(source, FirErrors.UNSUPPORTED, "literal prefixes and suffixes", context) + } +} diff --git a/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.fir.kt b/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.fir.kt deleted file mode 100644 index 5f1c5b78fb4..00000000000 --- a/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.fir.kt +++ /dev/null @@ -1,54 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER -USELESS_CAST - -infix fun Any?.foo(a: Any) {} -infix fun Any?.zoo(a: Any) {} -infix fun Any?.Loo(a: Any) {} -infix fun Any?.doo(a: Any) {} -infix fun Any?.ddoo(a: Any) {} -operator fun Any?.contains(a: Any): Boolean = true - -fun test(a: Any) { - 1foo a - 1ffoo a - 1doo a - 1ddoo a - 1contains a - - 1Lfoo a - 1Loo a - 1LLoo a - - 0b1foo a - 0b1Lfoo a - 0b1Loo a - 0b1LLoo a - - 0xfoo a - 0xffoo a - 0xfLLoo a - - 1.0foo a - 1.0ffoo a - 1.0doo a - 1.0ddoo a - - .0foo a - .0ffoo a - .0doo a - .0ddoo a - - 1in a - 1.0in a - 1.0fin a - 1.0din a - .0in a - .0fin a - .0din a - - 1is Any - 1as Any - 1as? Any - - 1!is Any - 1!in a -} diff --git a/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.kt b/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.kt index fa84137c5a2..bd2c8f2cf6e 100644 --- a/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.kt +++ b/compiler/testData/diagnostics/tests/NumberPrefixAndSuffix.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER -USELESS_CAST infix fun Any?.foo(a: Any) {} diff --git a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.fir.kt b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.fir.kt deleted file mode 100644 index 330d1f5c868..00000000000 --- a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.fir.kt +++ /dev/null @@ -1,40 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER - -infix fun Any?.foo(a: Any) {} -operator fun Any?.contains(a: Any): Boolean = true - -fun test(a: Any) { - - a foo"" - a foo"asd" - a foo"$a" - a foo"asd${a}sfsa" - a foo"""sdf""" - a foo'd' - a foo'' - - a foo""foo a - a foo"asd"foo a - a foo"$a"foo a - a foo"asd${a}sfsa"foo a - a foo"""sdf"""foo a - a foo'd'foo a - a foo''foo a - - a in"foo" - a in"""foo""" - a in's' - a in'' - - a !in"foo" - a !in"""foo""" - a !in's' - a !in'' - - if("s"is Any) {} - if("s"is Any) {} - test("s"as Any) - - a foo""1 - a foo""1.0 -} diff --git a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt index 129386d1840..5fe2ecf59c3 100644 --- a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt +++ b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER infix fun Any?.foo(a: Any) {}