[FIR] Refactoring for extended checkers

This commit is contained in:
vldf
2020-08-18 11:33:27 +03:00
parent e2016499f3
commit 0d6b363179
18 changed files with 165 additions and 150 deletions
@@ -5,12 +5,9 @@
package org.jetbrains.kotlin.fir.analysis.cfa package org.jetbrains.kotlin.fir.analysis.cfa
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
abstract class AbstractFirPropertyInitializationChecker { abstract class AbstractFirPropertyInitializationChecker {
@@ -20,89 +17,4 @@ abstract class AbstractFirPropertyInitializationChecker {
data: Map<CFGNode<*>, PropertyInitializationInfo>, data: Map<CFGNode<*>, PropertyInitializationInfo>,
properties: Set<FirPropertySymbol> properties: Set<FirPropertySymbol>
) )
class PropertyInitializationInfo(
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
) : ControlFlowInfo<PropertyInitializationInfo, FirPropertySymbol, EventOccurrencesRange>(map) {
companion object {
val EMPTY = PropertyInitializationInfo()
}
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
::PropertyInitializationInfo
fun merge(other: PropertyInitializationInfo): PropertyInitializationInfo {
var result = this
for (symbol in keys.union(other.keys)) {
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
result = result.put(symbol, kind1 or kind2)
}
return result
}
}
class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() {
companion object {
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> {
val collector = LocalPropertyCollector()
graph.traverse(TraverseDirection.Forward, collector)
return collector.symbols
}
}
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf()
override fun visitNode(node: CFGNode<*>) {}
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
symbols += node.fir.symbol
}
}
class DataCollector(private val localProperties: Set<FirPropertySymbol>) :
ControlFlowGraphVisitor<PropertyInitializationInfo, Collection<PropertyInitializationInfo>>() {
override fun visitNode(node: CFGNode<*>, data: Collection<PropertyInitializationInfo>): PropertyInitializationInfo {
if (data.isEmpty()) return PropertyInitializationInfo.EMPTY
return data.reduce(PropertyInitializationInfo::merge)
}
override fun visitVariableAssignmentNode(
node: VariableAssignmentNode,
data: Collection<PropertyInitializationInfo>
): PropertyInitializationInfo {
val dataForNode = visitNode(node, data)
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
return if (symbol !in localProperties) {
dataForNode
} else {
processVariableWithAssignment(dataForNode, symbol)
}
}
override fun visitVariableDeclarationNode(
node: VariableDeclarationNode,
data: Collection<PropertyInitializationInfo>
): PropertyInitializationInfo {
val dataForNode = visitNode(node, data)
return if (node.fir.initializer == null && node.fir.delegate == null) {
dataForNode
} else {
processVariableWithAssignment(dataForNode, node.fir.symbol)
}
}
fun getData(graph: ControlFlowGraph) =
graph.collectDataForNode(TraverseDirection.Forward, PropertyInitializationInfo.EMPTY, DataCollector(localProperties))
private fun processVariableWithAssignment(
dataForNode: PropertyInitializationInfo,
symbol: FirPropertySymbol
): PropertyInitializationInfo {
val existingKind = dataForNode[symbol] ?: EventOccurrencesRange.ZERO
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
return dataForNode.put(symbol, kind)
}
}
} }
@@ -0,0 +1,103 @@
/*
* Copyright 2010-2020 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.cfa
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
class PropertyInitializationInfo(
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
) : ControlFlowInfo<PropertyInitializationInfo, FirPropertySymbol, EventOccurrencesRange>(map) {
companion object {
val EMPTY = PropertyInitializationInfo()
}
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
::PropertyInitializationInfo
fun merge(other: PropertyInitializationInfo): PropertyInitializationInfo {
var result = this
for (symbol in keys.union(other.keys)) {
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
result = result.put(symbol, kind1 or kind2)
}
return result
}
}
class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() {
companion object {
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> {
val collector = LocalPropertyCollector()
graph.traverse(TraverseDirection.Forward, collector)
return collector.symbols
}
}
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf()
override fun visitNode(node: CFGNode<*>) {}
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
symbols += node.fir.symbol
}
}
class PropertyInitializationInfoCollector(private val localProperties: Set<FirPropertySymbol>) :
ControlFlowGraphVisitor<PropertyInitializationInfo, Collection<PropertyInitializationInfo>>() {
override fun visitNode(node: CFGNode<*>, data: Collection<PropertyInitializationInfo>): PropertyInitializationInfo {
if (data.isEmpty()) return PropertyInitializationInfo.EMPTY
return data.reduce(PropertyInitializationInfo::merge)
}
override fun visitVariableAssignmentNode(
node: VariableAssignmentNode,
data: Collection<PropertyInitializationInfo>
): PropertyInitializationInfo {
val dataForNode = visitNode(node, data)
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
return if (symbol !in localProperties) {
dataForNode
} else {
processVariableWithAssignment(dataForNode, symbol)
}
}
override fun visitVariableDeclarationNode(
node: VariableDeclarationNode,
data: Collection<PropertyInitializationInfo>
): PropertyInitializationInfo {
val dataForNode = visitNode(node, data)
return if (node.fir.initializer == null && node.fir.delegate == null) {
dataForNode
} else {
processVariableWithAssignment(dataForNode, node.fir.symbol)
}
}
fun getData(graph: ControlFlowGraph) =
graph.collectDataForNode(
TraverseDirection.Forward,
PropertyInitializationInfo.EMPTY,
this
)
private fun processVariableWithAssignment(
dataForNode: PropertyInitializationInfo,
symbol: FirPropertySymbol
): PropertyInitializationInfo {
val existingKind = dataForNode[symbol] ?: EventOccurrencesRange.ZERO
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
return dataForNode.put(symbol, kind)
}
}
@@ -47,9 +47,9 @@ class FirControlFlowAnalyzer(session: FirSession) {
} }
private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter) { private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
val properties = AbstractFirCfaPropertyAssignmentChecker.LocalPropertyCollector.collect(graph) val properties = LocalPropertyCollector.collect(graph)
if (properties.isEmpty()) return if (properties.isEmpty()) return
val data = AbstractFirCfaPropertyAssignmentChecker.DataCollector(properties).getData(graph) val data = PropertyInitializationInfoCollector(properties).getData(graph)
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties) } variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties) }
} }
} }
@@ -23,8 +23,8 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirSealedClassConstructorCallChecker : FirQualifiedAccessChecker() { object FirSealedClassConstructorCallChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val constructorFir = functionCall.calleeReference.safeAs<FirResolvedNamedReference>() val constructorFir = expression.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol ?.resolvedSymbol
?.fir.safeAs<FirConstructor>() ?.fir.safeAs<FirConstructor>()
?: return ?: return
@@ -39,7 +39,7 @@ object FirSealedClassConstructorCallChecker : FirQualifiedAccessChecker() {
?: return ?: return
if (typeFir.status.modality == Modality.SEALED) { if (typeFir.status.modality == Modality.SEALED) {
reporter.report(functionCall.calleeReference.source) reporter.report(expression.calleeReference.source)
} }
} }
@@ -21,9 +21,9 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirAbstractSuperCallChecker : FirQualifiedAccessChecker() { object FirAbstractSuperCallChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
// require the receiver to be the super reference // require the receiver to be the super reference
functionCall.explicitReceiver.safeAs<FirQualifiedAccessExpression>() expression.explicitReceiver.safeAs<FirQualifiedAccessExpression>()
?.calleeReference.safeAs<FirSuperReference>() ?.calleeReference.safeAs<FirSuperReference>()
?: return ?: return
@@ -32,7 +32,7 @@ object FirAbstractSuperCallChecker : FirQualifiedAccessChecker() {
if (closestClass.classKind == ClassKind.CLASS) { if (closestClass.classKind == ClassKind.CLASS) {
// handles all the FirSimpleFunction/FirProperty/etc. // handles all the FirSimpleFunction/FirProperty/etc.
val item = functionCall.getDeclaration<FirCallableMemberDeclaration<*>>() val item = expression.getDeclaration<FirCallableMemberDeclaration<*>>()
?: return ?: return
val declaration = item.getContainingClass(context).safeAs<FirRegularClass>() val declaration = item.getContainingClass(context).safeAs<FirRegularClass>()
@@ -42,7 +42,7 @@ object FirAbstractSuperCallChecker : FirQualifiedAccessChecker() {
declaration.modality == Modality.ABSTRACT && declaration.modality == Modality.ABSTRACT &&
item.modality == Modality.ABSTRACT item.modality == Modality.ABSTRACT
) { ) {
reporter.report(functionCall.calleeReference.source) reporter.report(expression.calleeReference.source)
} }
} }
} }
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
abstract class FirExpressionChecker<in E : FirStatement> { abstract class FirExpressionChecker<in E : FirStatement> {
abstract fun check(functionCall: E, context: CheckerContext, reporter: DiagnosticReporter) abstract fun check(expression: E, context: CheckerContext, reporter: DiagnosticReporter)
} }
typealias FirBasicExpresionChecker = FirExpressionChecker<FirStatement> typealias FirBasicExpresionChecker = FirExpressionChecker<FirStatement>
@@ -19,8 +19,8 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirNotASupertypeChecker : FirQualifiedAccessChecker() { object FirNotASupertypeChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val superReference = functionCall.calleeReference.safeAs<FirSuperReference>()?.takeIf { it.hadExplicitTypeInSource() } val superReference = expression.calleeReference.safeAs<FirSuperReference>()?.takeIf { it.hadExplicitTypeInSource() }
val targetClass = superReference val targetClass = superReference
?.superTypeRef ?.superTypeRef
@@ -29,7 +29,7 @@ object FirNotASupertypeChecker : FirQualifiedAccessChecker() {
val surrounding = context.findClosestClass(superReference.labelName) ?: return val surrounding = context.findClosestClass(superReference.labelName) ?: return
if (!targetClass.isSupertypeOf(surrounding)) { if (!targetClass.isSupertypeOf(surrounding)) {
reporter.report(functionCall.source) reporter.report(expression.source)
} }
} }
@@ -15,8 +15,8 @@ import org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance
import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.Variance
object FirProjectionsOnNonClassTypeArgumentChecker : FirQualifiedAccessChecker() { object FirProjectionsOnNonClassTypeArgumentChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
for (it in functionCall.typeArguments) { for (it in expression.typeArguments) {
when (it) { when (it) {
is FirStarProjection -> reporter.report(it.source) is FirStarProjection -> reporter.report(it.source)
is FirTypeProjectionWithVariance -> { is FirTypeProjectionWithVariance -> {
@@ -20,9 +20,9 @@ import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirQualifiedSupertypeExtendedByOtherSupertypeChecker : FirQualifiedAccessChecker() { object FirQualifiedSupertypeExtendedByOtherSupertypeChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
// require to be called over a super reference // require to be called over a super reference
val superReference = functionCall.calleeReference.safeAs<FirSuperReference>() val superReference = expression.calleeReference.safeAs<FirSuperReference>()
?.takeIf { it.hadExplicitTypeInSource() } ?.takeIf { it.hadExplicitTypeInSource() }
?: return ?: return
@@ -16,15 +16,15 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirSuperNotAvailableChecker : FirQualifiedAccessChecker() { object FirSuperNotAvailableChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall.calleeReference.safeAs<FirSuperReference>()?.hadExplicitTypeInSource() != true) return if (expression.calleeReference.safeAs<FirSuperReference>()?.hadExplicitTypeInSource() != true) return
val isInsideClass = context.containingDeclarations.any { val isInsideClass = context.containingDeclarations.any {
it.source?.elementType == CLASS || it.source?.elementType == OBJECT_LITERAL it.source?.elementType == CLASS || it.source?.elementType == OBJECT_LITERAL
} }
if (!isInsideClass) { if (!isInsideClass) {
reporter.report(functionCall.source) reporter.report(expression.source)
} }
} }
@@ -21,17 +21,17 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirSuperclassNotAccessibleFromInterfaceChecker : FirQualifiedAccessChecker() { object FirSuperclassNotAccessibleFromInterfaceChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val closestClass = context.findClosest<FirRegularClass>() ?: return val closestClass = context.findClosest<FirRegularClass>() ?: return
if (closestClass.classKind == ClassKind.INTERFACE) { if (closestClass.classKind == ClassKind.INTERFACE) {
val origin = getClassLikeDeclaration(functionCall, context) val origin = getClassLikeDeclaration(expression, context)
?.symbol.safeAs<FirRegularClassSymbol>() ?.symbol.safeAs<FirRegularClassSymbol>()
?.fir ?.fir
?: return ?: return
if (origin.source != null && origin.isSuperclassOf(closestClass)) { if (origin.source != null && origin.isSuperclassOf(closestClass)) {
reporter.report(functionCall.explicitReceiver?.source) reporter.report(expression.explicitReceiver?.source)
} }
} }
} }
@@ -23,10 +23,10 @@ import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.psi.KtTypeArgumentList import org.jetbrains.kotlin.psi.KtTypeArgumentList
object FirTypeArgumentsNotAllowedExpressionChecker : FirQualifiedAccessChecker() { object FirTypeArgumentsNotAllowedExpressionChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
// analyze type parameters near // analyze type parameters near
// package names // package names
val explicitReceiver = functionCall.explicitReceiver val explicitReceiver = expression.explicitReceiver
if (explicitReceiver is FirResolvedQualifier && explicitReceiver.symbol == null) { if (explicitReceiver is FirResolvedQualifier && explicitReceiver.symbol == null) {
if (explicitReceiver.source?.hasAnyArguments() == true) { if (explicitReceiver.source?.hasAnyArguments() == true) {
@@ -25,12 +25,12 @@ import org.jetbrains.kotlin.utils.addToStdlib.min
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
// something that contains the type parameters // something that contains the type parameters
// declarations with their declared bounds. // declarations with their declared bounds.
// it may be the called function declaration // it may be the called function declaration
// or the class declaration // or the class declaration
val calleeFir = functionCall.calleeReference.safeAs<FirResolvedNamedReference>() val calleeFir = expression.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol ?.resolvedSymbol
?.fir.safeAs<FirTypeParameterRefsOwner>() ?.fir.safeAs<FirTypeParameterRefsOwner>()
?: return ?: return
@@ -41,10 +41,10 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() {
) )
val parameterPairs = mutableMapOf<FirTypeParameterSymbol, FirResolvedTypeRef>() val parameterPairs = mutableMapOf<FirTypeParameterSymbol, FirResolvedTypeRef>()
val count = min(calleeFir.typeParameters.size, functionCall.typeArguments.size) val count = min(calleeFir.typeParameters.size, expression.typeArguments.size)
for (it in 0 until count) { for (it in 0 until count) {
functionCall.typeArguments[it].safeAs<FirTypeProjectionWithVariance>() expression.typeArguments[it].safeAs<FirTypeProjectionWithVariance>()
?.typeRef.safeAs<FirResolvedTypeRef>() ?.typeRef.safeAs<FirResolvedTypeRef>()
?.let { that -> ?.let { that ->
if (that !is FirErrorTypeRef) { if (that !is FirErrorTypeRef) {
@@ -92,7 +92,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() {
// typealias A<G> = B<List<G>> // typealias A<G> = B<List<G>>
// val a = A<Int>() // val a = A<Int>()
when (calleeFir) { when (calleeFir) {
is FirConstructor -> analyzeConstructorCall(functionCall, substitutor, typeCheckerContext, reporter) is FirConstructor -> analyzeConstructorCall(expression, substitutor, typeCheckerContext, reporter)
} }
} }
@@ -20,15 +20,15 @@ import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.coneType
object ArrayEqualityCanBeReplacedWithEquals : FirBasicExpresionChecker() { object ArrayEqualityCanBeReplacedWithEquals : FirBasicExpresionChecker() {
override fun check(functionCall: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall !is FirEqualityOperatorCall) return if (expression !is FirEqualityOperatorCall) return
if (functionCall.operation != FirOperation.EQ && functionCall.operation != FirOperation.NOT_EQ) return if (expression.operation != FirOperation.EQ && expression.operation != FirOperation.NOT_EQ) return
val left = functionCall.arguments.getOrNull(0) ?: return val left = expression.arguments.getOrNull(0) ?: return
val right = functionCall.arguments.getOrNull(1) ?: return val right = expression.arguments.getOrNull(1) ?: return
if (left.typeRef.coneType.classId != StandardClassIds.Array) return if (left.typeRef.coneType.classId != StandardClassIds.Array) return
if (right.typeRef.coneType.classId != StandardClassIds.Array) return if (right.typeRef.coneType.classId != StandardClassIds.Array) return
reporter.report(functionCall.psi?.children?.get(1)?.toFirPsiSourceElement(), ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS) reporter.report(expression.psi?.children?.get(1)?.toFirPsiSourceElement(), ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS)
} }
} }
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirExpressionChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirVariableAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
@@ -24,17 +24,17 @@ import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVariableAssignment>() { object CanBeReplacedWithOperatorAssignmentChecker : FirVariableAssignmentChecker() {
override fun check(assignment: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
val lValue = assignment.lValue val lValue = expression.lValue
if (lValue !is FirResolvedNamedReference) return if (lValue !is FirResolvedNamedReference) return
val operator = assignment.psi?.children?.getOrNull(1) ?: return val operator = expression.psi?.children?.getOrNull(1) ?: return
val operationSign = (operator as? KtOperationReferenceExpression)?.operationSignTokenType val operationSign = (operator as? KtOperationReferenceExpression)?.operationSignTokenType
if (operationSign != KtTokens.EQ) return if (operationSign != KtTokens.EQ) return
val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return
val rValue = assignment.rValue as? FirFunctionCall ?: return val rValue = expression.rValue as? FirFunctionCall ?: return
val rValuePsi = rValue.psi as? KtBinaryExpression ?: return val rValuePsi = rValue.psi as? KtBinaryExpression ?: return
val rValueClassId = rValue.explicitReceiver?.typeRef?.coneType?.classId val rValueClassId = rValue.explicitReceiver?.typeRef?.coneType?.classId
@@ -60,11 +60,9 @@ object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVari
val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken) val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
&& leftExpression?.matcher(variable) == true && leftExpression?.matcher(variable) == true
if (isLeftMatch) return true if (isLeftMatch) return true
val isRightMatch = isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
&& rightExpression?.matcher(variable) == true
if (isRightMatch) return true
false return (isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
&& rightExpression?.matcher(variable) == true)
} else { } else {
val leftExpression = left as? KtBinaryExpression val leftExpression = left as? KtBinaryExpression
@@ -16,13 +16,13 @@ import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall
object EmptyRangeChecker : FirBasicExpresionChecker() { object EmptyRangeChecker : FirBasicExpresionChecker() {
override fun check(functionCall: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall.source is FirFakeSourceElement<*>) return if (expression.source is FirFakeSourceElement<*>) return
if (functionCall !is FirFunctionCall) return if (expression !is FirFunctionCall) return
val left = functionCall.rangeLeft ?: return val left = expression.rangeLeft ?: return
val right = functionCall.rangeRight ?: return val right = expression.rangeRight ?: return
val needReport = when (functionCall.calleeReference.name.asString()) { val needReport = when (expression.calleeReference.name.asString()) {
"rangeTo" -> { "rangeTo" -> {
left > right left > right
} }
@@ -36,7 +36,7 @@ object EmptyRangeChecker : FirBasicExpresionChecker() {
} }
if (needReport) { if (needReport) {
reporter.report(functionCall.source, FirErrors.EMPTY_RANGE) reporter.report(expression.source, FirErrors.EMPTY_RANGE)
} }
} }
@@ -57,6 +57,7 @@ object EmptyRangeChecker : FirBasicExpresionChecker() {
else (arg as? FirConstExpression<*>)?.value as? Long else (arg as? FirConstExpression<*>)?.value as? Long
} }
// todo: add proper integer operator calls checking (e.g. (1+2)*3 transforms to 9)
private val FirIntegerOperatorCall.asLong: Long? private val FirIntegerOperatorCall.asLong: Long?
get() { get() {
val value = (dispatchReceiver as? FirConstExpression<*>)?.value as Long? ?: return null val value = (dispatchReceiver as? FirConstExpression<*>)?.value as Long? ?: return null
@@ -26,14 +26,14 @@ import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.types.expressions.OperatorConventions
object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() { object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall !is FirFunctionCall) return if (expression !is FirFunctionCall) return
if (functionCall.source?.kind == FirFakeSourceElementKind.GeneratedToStringCallOnTemplateEntry) return if (expression.source?.kind == FirFakeSourceElementKind.GeneratedToStringCallOnTemplateEntry) return
val functionName = functionCall.calleeReference.name.asString() val functionName = expression.calleeReference.name.asString()
val qualifiedType = targetClassMap[functionName] ?: return val qualifiedType = targetClassMap[functionName] ?: return
if (functionCall.explicitReceiver?.isRedundant(qualifiedType) == true) { if (expression.explicitReceiver?.isRedundant(qualifiedType) == true) {
reporter.report(functionCall.source, FirErrors.REDUNDANT_CALL_OF_CONVERSION_METHOD) reporter.report(expression.source, FirErrors.REDUNDANT_CALL_OF_CONVERSION_METHOD)
} }
} }
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.FirFakeSourceElement import org.jetbrains.kotlin.fir.FirFakeSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo
import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection
import org.jetbrains.kotlin.fir.analysis.cfa.traverse import org.jetbrains.kotlin.fir.analysis.cfa.traverse
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter