[FIR] Implement RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER

Split underscore checkers
This commit is contained in:
Ivan Kochurkin
2021-07-23 15:49:20 +03:00
committed by Space
parent 10d9988824
commit 8bfaa39a5c
15 changed files with 297 additions and 194 deletions
@@ -1070,6 +1070,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val UNDERSCORE_IS_RESERVED by error<PsiElement>(PositioningStrategy.NAME_IDENTIFIER) val UNDERSCORE_IS_RESERVED by error<PsiElement>(PositioningStrategy.NAME_IDENTIFIER)
val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error<PsiElement>(PositioningStrategy.NAME_IDENTIFIER) val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error<PsiElement>(PositioningStrategy.NAME_IDENTIFIER)
val RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER by warning<KtNameReferenceExpression>()
val INVALID_CHARACTERS by error<KtNamedDeclaration>(PositioningStrategy.NAME_IDENTIFIER) { val INVALID_CHARACTERS by error<KtNamedDeclaration>(PositioningStrategy.NAME_IDENTIFIER) {
parameter<String>("message") parameter<String>("message")
} }
@@ -57,6 +57,7 @@ import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
@@ -556,6 +557,7 @@ object FirErrors {
val DELEGATE_SPECIAL_FUNCTION_RETURN_TYPE_MISMATCH by error3<KtExpression, String, ConeKotlinType, ConeKotlinType>() val DELEGATE_SPECIAL_FUNCTION_RETURN_TYPE_MISMATCH by error3<KtExpression, String, ConeKotlinType, ConeKotlinType>()
val UNDERSCORE_IS_RESERVED by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER) val UNDERSCORE_IS_RESERVED by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER) val UNDERSCORE_USAGE_WITHOUT_BACKTICKS by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
val RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER by warning0<KtNameReferenceExpression>()
val INVALID_CHARACTERS by error1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER) val INVALID_CHARACTERS by error1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
val DANGEROUS_CHARACTERS by warning1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER) val DANGEROUS_CHARACTERS by warning1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
val EQUALITY_NOT_APPLICABLE by error3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>() val EQUALITY_NOT_APPLICABLE by error3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnonymousFunctionParametersChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnonymousFunctionParametersChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirReservedUnderscoreDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirAnonymousFunctionSyntaxChecker import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirAnonymousFunctionSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirDelegationInInterfaceSyntaxChecker import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirDelegationInInterfaceSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirFunctionTypeParametersSyntaxChecker import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirFunctionTypeParametersSyntaxChecker
@@ -16,7 +16,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
override val basicExpressionCheckers: Set<FirBasicExpressionChecker> override val basicExpressionCheckers: Set<FirBasicExpressionChecker>
get() = setOf( get() = setOf(
FirReservedUnderscoreExpressionChecker, FirUnderscoreChecker,
FirExpressionAnnotationChecker, FirExpressionAnnotationChecker,
FirDeprecationChecker FirDeprecationChecker
) )
@@ -0,0 +1,35 @@
/*
* 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
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
fun checkUnderscoreDiagnostics(
source: FirSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
isExpression: Boolean
) {
if (source != null && source.kind !is FirFakeSourceElementKind) {
with(SourceNavigator.forSource(source)) {
if (source.getRawIdentifier()?.isUnderscore == true) {
reporter.reportOn(
source,
if (isExpression) FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS else FirErrors.UNDERSCORE_IS_RESERVED,
context
)
}
}
}
}
val CharSequence.isUnderscore: Boolean
get() = all { it == '_' }
@@ -6,15 +6,22 @@
package org.jetbrains.kotlin.fir.analysis.checkers package org.jetbrains.kotlin.fir.analysis.checkers
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirLightSourceElement import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.diagnostics.getAncestors import org.jetbrains.kotlin.fir.analysis.diagnostics.getAncestors
import org.jetbrains.kotlin.fir.analysis.diagnostics.nameIdentifier
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.stubs.elements.KtNameReferenceExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtTypeProjectionElementType
/** /**
* Service to answer source-related questions in generic fashion. * Service to answer source-related questions in generic fashion.
@@ -26,17 +33,27 @@ interface SourceNavigator {
fun FirTypeRef.isInTypeConstraint(): Boolean fun FirTypeRef.isInTypeConstraint(): Boolean
fun FirSourceElement.getRawIdentifier(): String?
fun FirDeclaration.getRawName(): String?
fun FirValueParameterSymbol.isCatchElementParameter(): Boolean
companion object { companion object {
private val lightTreeInstance = LightTreeSourceNavigator() private val lightTreeInstance = LightTreeSourceNavigator()
fun forElement(e: FirElement): SourceNavigator = when (e.source) { fun forElement(e: FirElement): SourceNavigator = forSource(e.source)
fun forSource(e: FirSourceElement?): SourceNavigator = when (e) {
is FirLightSourceElement -> lightTreeInstance is FirLightSourceElement -> lightTreeInstance
is FirPsiSourceElement -> PsiSourceNavigator is FirPsiSourceElement -> PsiSourceNavigator
null -> lightTreeInstance //shouldn't matter null -> lightTreeInstance //shouldn't matter
} }
inline fun <R> FirElement.withNavigator(block: SourceNavigator.() -> R): R = with(forElement(this), block) inline fun <R> FirElement.withNavigator(block: SourceNavigator.() -> R): R = with(forSource(this.source), block)
inline fun <R> FirSourceElement.withNavigator(block: SourceNavigator.() -> R): R = with(forSource(this), block)
} }
} }
@@ -55,16 +72,61 @@ open class LightTreeSourceNavigator : SourceNavigator {
.find { it.tokenType == KtNodeTypes.TYPE_CONSTRAINT || it.tokenType == KtNodeTypes.TYPE_PARAMETER } .find { it.tokenType == KtNodeTypes.TYPE_CONSTRAINT || it.tokenType == KtNodeTypes.TYPE_PARAMETER }
?.tokenType == KtNodeTypes.TYPE_CONSTRAINT ?.tokenType == KtNodeTypes.TYPE_CONSTRAINT
} }
override fun FirSourceElement.getRawIdentifier(): String? {
val tokenType = elementType
return if (tokenType is KtNameReferenceExpressionElementType || tokenType == KtTokens.IDENTIFIER) {
lighterASTNode.toString()
} else if (tokenType is KtTypeProjectionElementType) {
lighterASTNode.getChildren(treeStructure).last().toString()
} else {
null
}
}
override fun FirDeclaration.getRawName(): String? {
return source?.let { it.treeStructure.nameIdentifier(it.lighterASTNode)?.toString() }
}
override fun FirValueParameterSymbol.isCatchElementParameter(): Boolean {
val localSource = source ?: return false
var parent = localSource.treeStructure.getParent(localSource.lighterASTNode)
parent?.let { parent = localSource.treeStructure.getParent(it) }
return parent?.tokenType == KtNodeTypes.CATCH
}
} }
//by default psi tree can reuse light tree manipulations //by default psi tree can reuse light tree manipulations
object PsiSourceNavigator : LightTreeSourceNavigator() { object PsiSourceNavigator : LightTreeSourceNavigator() {
//Swallows incorrect casts!!! //Swallows incorrect casts!!!
private inline fun <reified P : PsiElement> FirElement.psi(): P? { private inline fun <reified P : PsiElement> FirElement.psi(): P? = source?.psi()
val psi = (source as? FirPsiSourceElement)?.psi
private inline fun <reified P : PsiElement> FirSourceElement.psi(): P? {
val psi = (this as? FirPsiSourceElement)?.psi
return psi as? P return psi as? P
} }
override fun FirTypeRef.isInConstructorCallee(): Boolean = psi<KtTypeReference>()?.parent is KtConstructorCalleeExpression override fun FirTypeRef.isInConstructorCallee(): Boolean = psi<KtTypeReference>()?.parent is KtConstructorCalleeExpression
override fun FirSourceElement.getRawIdentifier(): String? {
val psi = psi<PsiElement>()
return if (psi is KtNameReferenceExpression) {
psi.getReferencedNameElement().node.text
} else if (psi is KtTypeProjection) {
psi.typeReference?.typeElement?.text
} else if (psi is LeafPsiElement && psi.elementType == KtTokens.IDENTIFIER) {
psi.text
} else {
null
}
}
override fun FirDeclaration.getRawName(): String? {
return (this.psi() as? PsiNameIdentifierOwner)?.nameIdentifier?.text
}
override fun FirValueParameterSymbol.isCatchElementParameter(): Boolean {
return source?.psi<PsiElement>()?.parent?.parent is KtCatchClause
}
} }
@@ -0,0 +1,89 @@
/*
* 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.declaration
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator
import org.jetbrains.kotlin.fir.analysis.checkers.checkUnderscoreDiagnostics
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.isUnderscore
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.declarations.*
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.FirUserTypeRef
object FirReservedUnderscoreDeclarationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (
declaration is FirRegularClass ||
declaration is FirTypeParameter ||
declaration is FirProperty ||
declaration is FirTypeAlias
) {
reportIfUnderscore(declaration, context, reporter)
} else if (declaration is FirFunction) {
if (declaration is FirSimpleFunction) {
reportIfUnderscore(declaration, context, reporter)
}
val isSingleUnderscoreAllowed = declaration is FirAnonymousFunction || declaration is FirPropertyAccessor
for (parameter in declaration.valueParameters) {
reportIfUnderscore(
parameter,
context,
reporter,
isSingleUnderscoreAllowed = isSingleUnderscoreAllowed
)
}
} else if (declaration is FirFile) {
for (import in declaration.imports) {
checkUnderscoreDiagnostics(import.aliasSource, context, reporter, isExpression = false)
}
}
}
private fun reportIfUnderscore(
declaration: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
isSingleUnderscoreAllowed: Boolean = false
) {
val declarationSource = declaration.source
if (declarationSource != null && declarationSource.kind !is FirFakeSourceElementKind) {
with(SourceNavigator.forElement(declaration)) {
val rawName = declaration.getRawName()
if (rawName?.isUnderscore == true && !(isSingleUnderscoreAllowed && rawName == "_")) {
reporter.reportOn(
declarationSource,
FirErrors.UNDERSCORE_IS_RESERVED,
context
)
}
}
}
val returnOrReceiverTypeRef = when (declaration) {
is FirValueParameter -> declaration.returnTypeRef
is FirFunction -> declaration.receiverTypeRef
else -> null
}
if (returnOrReceiverTypeRef is FirResolvedTypeRef) {
val delegatedTypeRef = returnOrReceiverTypeRef.delegatedTypeRef
if (delegatedTypeRef is FirUserTypeRef) {
for (qualifierPart in delegatedTypeRef.qualifier) {
checkUnderscoreDiagnostics(qualifierPart.source, context, reporter, isExpression = true)
for (typeArgument in qualifierPart.typeArgumentList.typeArguments) {
checkUnderscoreDiagnostics(typeArgument.source, context, reporter, isExpression = true)
}
}
}
}
}
}
@@ -1,171 +0,0 @@
/*
* 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.PsiNameIdentifierOwner
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getChildren
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.diagnostics.ConeUnderscoreUsageWithoutBackticks
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.stubs.elements.KtNameReferenceExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtTypeProjectionElementType
object FirReservedUnderscoreExpressionChecker : FirBasicExpressionChecker() {
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
when (expression) {
is FirResolvable -> {
reportIfUnderscore(expression.calleeReference.source, context, reporter, isExpression = true)
}
is FirResolvedQualifier -> {
for (reservedUnderscoreDiagnostic in expression.nonFatalDiagnostics.filterIsInstance<ConeUnderscoreUsageWithoutBackticks>()) {
reporter.reportOn(reservedUnderscoreDiagnostic.source, FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS, context)
}
}
}
}
}
object FirReservedUnderscoreDeclarationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (
declaration is FirRegularClass ||
declaration is FirTypeParameter ||
declaration is FirProperty ||
declaration is FirTypeAlias
) {
reportIfUnderscore(declaration, context, reporter)
} else if (declaration is FirFunction) {
if (declaration is FirSimpleFunction) {
reportIfUnderscore(declaration, context, reporter)
}
val isSingleUnderscoreAllowed = declaration is FirAnonymousFunction || declaration is FirPropertyAccessor
for (parameter in declaration.valueParameters) {
reportIfUnderscore(
parameter,
context,
reporter,
isSingleUnderscoreAllowed = isSingleUnderscoreAllowed
)
}
} else if (declaration is FirFile) {
for (import in declaration.imports) {
reportIfUnderscore(import.aliasSource, context, reporter, isExpression = false)
}
}
}
}
private fun reportIfUnderscore(
declaration: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
isSingleUnderscoreAllowed: Boolean = false
) {
val declarationSource = declaration.source
val rawName = when (declarationSource) {
is FirPsiSourceElement ->
(declarationSource.psi as? PsiNameIdentifierOwner)?.nameIdentifier?.text
is FirLightSourceElement ->
declarationSource.treeStructure.nameIdentifier(declarationSource.lighterASTNode)?.toString()
else ->
null
}
reportIfUnderscore(rawName, declarationSource, context, reporter, isSingleUnderscoreAllowed)
val returnOrReceiverTypeRef = when (declaration) {
is FirValueParameter -> declaration.returnTypeRef
is FirFunction -> declaration.receiverTypeRef
else -> null
}
if (returnOrReceiverTypeRef is FirResolvedTypeRef) {
val delegatedTypeRef = returnOrReceiverTypeRef.delegatedTypeRef
if (delegatedTypeRef is FirUserTypeRef) {
for (qualifierPart in delegatedTypeRef.qualifier) {
reportIfUnderscore(qualifierPart.source, context, reporter, isExpression = true)
for (typeArgument in qualifierPart.typeArgumentList.typeArguments) {
reportIfUnderscore(typeArgument.source, context, reporter, isExpression = true)
}
}
}
}
}
private fun reportIfUnderscore(
source: FirSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
isExpression: Boolean
) {
if (source != null && source.kind !is FirFakeSourceElementKind) {
var rawName: String? = null
if (source is FirPsiSourceElement) {
val psi = source.psi
rawName = if (psi is KtNameReferenceExpression) {
psi.getReferencedNameElement().node.text
} else if (psi is KtTypeProjection) {
psi.typeReference?.typeElement?.text
} else if (psi is LeafPsiElement && psi.elementType == KtTokens.IDENTIFIER) {
psi.text
} else {
null
}
} else if (source is FirLightSourceElement) {
val tokenType = source.elementType
rawName = if (tokenType is KtNameReferenceExpressionElementType || tokenType == KtTokens.IDENTIFIER) {
source.lighterASTNode.toString()
} else if (tokenType is KtTypeProjectionElementType) {
source.lighterASTNode.getChildren(source.treeStructure).last().toString()
} else {
null
}
}
if (rawName?.isUnderscore == true) {
reporter.reportOn(
source,
if (isExpression) FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS else FirErrors.UNDERSCORE_IS_RESERVED,
context
)
}
}
}
private fun reportIfUnderscore(
rawName: CharSequence?,
source: FirSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
isSingleUnderscoreAllowed: Boolean
) {
if (source == null || rawName == null || isSingleUnderscoreAllowed && rawName == "_") {
return
}
if (rawName.isUnderscore && source.kind !is FirFakeSourceElementKind) {
reporter.reportOn(
source,
FirErrors.UNDERSCORE_IS_RESERVED,
context
)
}
}
val CharSequence.isUnderscore: Boolean
get() = all { it == '_' }
@@ -0,0 +1,56 @@
/*
* 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 org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.checkUnderscoreDiagnostics
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.diagnostics.ConeUnderscoreUsageWithoutBackticks
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator
object FirUnderscoreChecker : FirBasicExpressionChecker() {
private val singleUnderscore: Name = Name.identifier("_")
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
when (expression) {
is FirResolvable -> {
checkUnderscoreDiagnostics(expression.calleeReference.source, context, reporter, true)
checkResolvedToUnderscoreNamedCatchParameter(expression, context, reporter)
}
is FirResolvedQualifier -> {
for (reservedUnderscoreDiagnostic in expression.nonFatalDiagnostics.filterIsInstance<ConeUnderscoreUsageWithoutBackticks>()) {
reporter.reportOn(reservedUnderscoreDiagnostic.source, FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS, context)
}
}
}
}
private fun checkResolvedToUnderscoreNamedCatchParameter(
expression: FirResolvable,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val calleeReference = expression.calleeReference
val symbol = calleeReference.toResolvedCallableSymbol()
with(SourceNavigator.forElement(expression)) {
if (symbol is FirValueParameterSymbol && symbol.isCatchElementParameter() && symbol.name == singleUnderscore) {
reporter.reportOn(
calleeReference.source,
FirErrors.RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER,
context
)
}
}
}
}
@@ -340,6 +340,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_BOUND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_MODIFIER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_MODIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESERVED_MEMBER_INSIDE_INLINE_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESERVED_MEMBER_INSIDE_INLINE_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESOLUTION_TO_CLASSIFIER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESOLUTION_TO_CLASSIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESULT_TYPE_MISMATCH import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RESULT_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RETURN_FOR_BUILT_IN_SUSPEND import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RETURN_FOR_BUILT_IN_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY
@@ -1400,6 +1401,10 @@ class FirDefaultErrorMessages {
UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNDERSCORE_USAGE_WITHOUT_BACKTICKS,
"Names _, __, ___, ... can be used only in back-ticks (`_`, `__`, `___`, ...)" "Names _, __, ___, ... can be used only in back-ticks (`_`, `__`, `___`, ...)"
) )
map.put(
RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER,
"Referencing to an underscore-named parameter is deprecated. It will be an error in a future release."
)
map.put( map.put(
INVALID_CHARACTERS, INVALID_CHARACTERS,
"Name {0}", "Name {0}",
@@ -838,12 +838,17 @@ object PositioningStrategies {
} }
val NAME_IDENTIFIER: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() { val NAME_IDENTIFIER: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
private fun KtTypeElement.getReferencedTypeExpression(): KtElement? { override fun mark(element: PsiElement): List<TextRange> {
return when (this) { if (element is PsiNameIdentifierOwner) {
is KtUserType -> referenceExpression val nameIdentifier = element.nameIdentifier
is KtNullableType -> innerType?.getReferencedTypeExpression() if (nameIdentifier != null) {
is KtDefinitelyNotNullType -> innerType?.getReferencedTypeExpression() return super.mark(nameIdentifier)
else -> null }
} else if (element is KtLabelReferenceExpression) {
return super.mark(element.getReferencedNameElement())
}
return DEFAULT.mark(element)
} }
} }
@@ -6,7 +6,7 @@ fun foo() {
try { try {
TODO() TODO()
} catch (_: Exception) { } catch (_: Exception) {
`_`.stackTrace <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>.stackTrace
} }
try { try {
TODO() TODO()
@@ -17,20 +17,20 @@ fun foo() {
val x4 = { _: Int -> val x4 = { _: Int ->
`_` `_`
} }
`_` <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>
} }
`_` <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>
10 10
} }
fun bar(x: Exception = `_`) {} fun bar(x: Exception = <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>) {}
class Bar(`_`: Exception = <!UNINITIALIZED_PARAMETER!>`_`<!>) { class Bar(`_`: Exception = <!UNINITIALIZED_PARAMETER!>`_`<!>) {
inner class Bar2(x: Exception = `_`) { } inner class Bar2(x: Exception = <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>) { }
} }
} }
} catch (_: Exception) { } catch (_: Exception) {
`_`.stackTrace <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>.stackTrace
val y1 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS!>_<!> val y1 = <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER, UNDERSCORE_USAGE_WITHOUT_BACKTICKS!>_<!>
val y2 = (`_`) val y2 = (<!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>)
} }
try { try {
TODO() TODO()
@@ -38,7 +38,7 @@ fun foo() {
try { try {
TODO() TODO()
} catch (x: Exception) { } catch (x: Exception) {
`_`.stackTrace <!RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER!>`_`<!>.stackTrace
} }
} }
val boo1 = { `_`: Exception -> val boo1 = { `_`: Exception ->
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
@@ -2876,6 +2877,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, token,
) )
} }
add(FirErrors.RESOLVED_TO_UNDERSCORE_NAMED_CATCH_PARAMETER) { firDiagnostic ->
ResolvedToUnderscoreNamedCatchParameterImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.INVALID_CHARACTERS) { firDiagnostic -> add(FirErrors.INVALID_CHARACTERS) { firDiagnostic ->
InvalidCharactersImpl( InvalidCharactersImpl(
firDiagnostic.a, firDiagnostic.a,
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
@@ -2011,6 +2012,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = UnderscoreUsageWithoutBackticks::class override val diagnosticClass get() = UnderscoreUsageWithoutBackticks::class
} }
abstract class ResolvedToUnderscoreNamedCatchParameter : KtFirDiagnostic<KtNameReferenceExpression>() {
override val diagnosticClass get() = ResolvedToUnderscoreNamedCatchParameter::class
}
abstract class InvalidCharacters : KtFirDiagnostic<KtNamedDeclaration>() { abstract class InvalidCharacters : KtFirDiagnostic<KtNamedDeclaration>() {
override val diagnosticClass get() = InvalidCharacters::class override val diagnosticClass get() = InvalidCharacters::class
abstract val message: String abstract val message: String
@@ -53,6 +53,7 @@ import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
@@ -3239,6 +3240,13 @@ internal class UnderscoreUsageWithoutBackticksImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
} }
internal class ResolvedToUnderscoreNamedCatchParameterImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ResolvedToUnderscoreNamedCatchParameter(), KtAbstractFirDiagnostic<KtNameReferenceExpression> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class InvalidCharactersImpl( internal class InvalidCharactersImpl(
override val message: String, override val message: String,
firDiagnostic: FirPsiDiagnostic, firDiagnostic: FirPsiDiagnostic,