[FIR] Checkers performance improvement
[FIR] Checkers performance improvement
This commit is contained in:
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.FirSessionComponent
|
import org.jetbrains.kotlin.fir.FirSessionComponent
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
||||||
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.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
|
||||||
@@ -70,6 +71,8 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
get() = _constructorCheckers
|
get() = _constructorCheckers
|
||||||
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker>
|
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker>
|
||||||
get() = _controlFlowAnalyserCheckers
|
get() = _controlFlowAnalyserCheckers
|
||||||
|
override val variableAssignmentCfaBasedCheckers: List<AbstractFirPropertyInitializationChecker>
|
||||||
|
get() = _variableAssignmentCfaBasedCheckers
|
||||||
|
|
||||||
private val _fileCheckers: MutableList<FirFileChecker> = mutableListOf()
|
private val _fileCheckers: MutableList<FirFileChecker> = mutableListOf()
|
||||||
private val _declarationCheckers: MutableList<FirBasicDeclarationChecker> = mutableListOf()
|
private val _declarationCheckers: MutableList<FirBasicDeclarationChecker> = mutableListOf()
|
||||||
@@ -77,6 +80,7 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
private val _regularClassCheckers: MutableList<FirRegularClassChecker> = mutableListOf()
|
private val _regularClassCheckers: MutableList<FirRegularClassChecker> = mutableListOf()
|
||||||
private val _constructorCheckers: MutableList<FirConstructorChecker> = mutableListOf()
|
private val _constructorCheckers: MutableList<FirConstructorChecker> = mutableListOf()
|
||||||
private val _controlFlowAnalyserCheckers: MutableList<FirControlFlowChecker> = mutableListOf()
|
private val _controlFlowAnalyserCheckers: MutableList<FirControlFlowChecker> = mutableListOf()
|
||||||
|
private val _variableAssignmentCfaBasedCheckers: MutableList<AbstractFirPropertyInitializationChecker> = mutableListOf()
|
||||||
|
|
||||||
fun register(checkers: DeclarationCheckers) {
|
fun register(checkers: DeclarationCheckers) {
|
||||||
_fileCheckers += checkers.allFileCheckers
|
_fileCheckers += checkers.allFileCheckers
|
||||||
@@ -85,6 +89,7 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
_regularClassCheckers += checkers.allRegularClassCheckers
|
_regularClassCheckers += checkers.allRegularClassCheckers
|
||||||
_constructorCheckers += checkers.allConstructorCheckers
|
_constructorCheckers += checkers.allConstructorCheckers
|
||||||
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
|
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
|
||||||
|
_variableAssignmentCfaBasedCheckers += checkers.variableAssignmentCfaBasedCheckers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-93
@@ -5,100 +5,16 @@
|
|||||||
|
|
||||||
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.checkers.cfa.FirControlFlowChecker
|
|
||||||
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 : FirControlFlowChecker() {
|
abstract class AbstractFirPropertyInitializationChecker {
|
||||||
abstract override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter)
|
abstract fun analyze(
|
||||||
|
graph: ControlFlowGraph,
|
||||||
class PropertyInitializationInfo(
|
reporter: DiagnosticReporter,
|
||||||
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
||||||
) : ControlFlowInfo<PropertyInitializationInfo, FirPropertySymbol, EventOccurrencesRange>(map) {
|
properties: Set<FirPropertySymbol>
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-4
@@ -12,23 +12,44 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
|||||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||||
|
|
||||||
class FirControlFlowAnalyzer(session: FirSession) {
|
class FirControlFlowAnalyzer(session: FirSession) {
|
||||||
private val checkers = session.checkersComponent.declarationCheckers.controlFlowAnalyserCheckers
|
private val cfaCheckers = session.checkersComponent.declarationCheckers.controlFlowAnalyserCheckers
|
||||||
|
private val variableAssignmentCheckers = session.checkersComponent.declarationCheckers.variableAssignmentCfaBasedCheckers
|
||||||
|
|
||||||
fun analyzeClassInitializer(klass: FirClass<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzeClassInitializer(klass: FirClass<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (graph.owner != null) return
|
if (graph.owner != null) return
|
||||||
checkers.forEach { it.analyze(graph, reporter) }
|
cfaCheckers.forEach { it.analyze(graph, reporter) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun analyzeFunction(function: FirFunction<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzeFunction(function: FirFunction<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (graph.owner != null) return
|
if (graph.owner != null) return
|
||||||
checkers.forEach { it.analyze(graph, reporter) }
|
|
||||||
|
cfaCheckers.forEach { it.analyze(graph, reporter) }
|
||||||
|
if (context.containingDeclarations.any { it is FirProperty || it is FirFunction<*> }) return
|
||||||
|
runAssignmentCfaCheckers(graph, reporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun analyzePropertyInitializer(property: FirProperty, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzePropertyInitializer(property: FirProperty, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (graph.owner != null) return
|
if (graph.owner != null) return
|
||||||
checkers.forEach { it.analyze(graph, reporter) }
|
|
||||||
|
cfaCheckers.forEach { it.analyze(graph, reporter) }
|
||||||
|
runAssignmentCfaCheckers(graph, reporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun analyzePropertyAccessor(accessor: FirPropertyAccessor, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
|
if (graph.owner != null) return
|
||||||
|
|
||||||
|
cfaCheckers.forEach { it.analyze(graph, reporter) }
|
||||||
|
runAssignmentCfaCheckers(graph, reporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
||||||
|
val properties = LocalPropertyCollector.collect(graph)
|
||||||
|
if (properties.isEmpty()) return
|
||||||
|
val data = PropertyInitializationInfoCollector(properties).getData(graph)
|
||||||
|
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -16,15 +16,23 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
|||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.QualifiedAccessNode
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.QualifiedAccessNode
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||||
|
|
||||||
object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChecker() {
|
object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChecker() {
|
||||||
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
override fun analyze(
|
||||||
val localProperties = LocalPropertyCollector.collect(graph)
|
graph: ControlFlowGraph,
|
||||||
// we want to analyze only properties without initializers
|
reporter: DiagnosticReporter,
|
||||||
localProperties.retainAll { it.fir.initializer == null && it.fir.delegate == null }
|
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
||||||
if (localProperties.isEmpty()) return
|
properties: Set<FirPropertySymbol>
|
||||||
val data = DataCollector(localProperties).getData(graph)
|
) {
|
||||||
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, reporter)
|
val localData = data.filter {
|
||||||
|
val symbolFir = (it.key.fir as? FirVariableSymbol<*>)?.fir
|
||||||
|
symbolFir == null || symbolFir.initializer == null && symbolFir.delegate == null
|
||||||
|
}
|
||||||
|
|
||||||
|
val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet()
|
||||||
|
|
||||||
|
val reporterVisitor = UninitializedPropertyReporter(localData, localProperties, reporter)
|
||||||
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
|||||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||||
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
||||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtModifierList
|
import org.jetbrains.kotlin.psi.KtModifierList
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
||||||
@@ -186,7 +185,7 @@ fun FirClassLikeDeclaration<*>.followAllAlias(session: FirSession): FirClassLike
|
|||||||
* or null if no such item could be found.
|
* or null if no such item could be found.
|
||||||
*/
|
*/
|
||||||
fun CheckerContext.findClosestClassOrObject(): FirClass<*>? {
|
fun CheckerContext.findClosestClassOrObject(): FirClass<*>? {
|
||||||
for (it in containingDeclarations.reversed()) {
|
for (it in containingDeclarations.asReversed()) {
|
||||||
if (
|
if (
|
||||||
it is FirRegularClass ||
|
it is FirRegularClass ||
|
||||||
it is FirAnonymousObject
|
it is FirAnonymousObject
|
||||||
@@ -248,20 +247,20 @@ fun FirClass<*>.modality(): Modality? {
|
|||||||
/**
|
/**
|
||||||
* returns implicit modality by FirMemberDeclaration
|
* returns implicit modality by FirMemberDeclaration
|
||||||
*/
|
*/
|
||||||
fun FirMemberDeclaration.implicitModality(context: CheckerContext): KtModifierKeywordToken {
|
fun FirMemberDeclaration.implicitModality(context: CheckerContext): Modality {
|
||||||
if (this is FirRegularClass && (this.classKind == ClassKind.CLASS || this.classKind == ClassKind.OBJECT)) {
|
if (this is FirRegularClass && (this.classKind == ClassKind.CLASS || this.classKind == ClassKind.OBJECT)) {
|
||||||
if (this.classKind == ClassKind.INTERFACE) return KtTokens.ABSTRACT_KEYWORD
|
if (this.classKind == ClassKind.INTERFACE) return Modality.ABSTRACT
|
||||||
return KtTokens.FINAL_KEYWORD
|
return Modality.FINAL
|
||||||
}
|
}
|
||||||
|
|
||||||
val klass = context.findClosestClassOrObject() ?: return KtTokens.FINAL_KEYWORD
|
val klass = context.findClosestClassOrObject() ?: return Modality.FINAL
|
||||||
val modifiers = this.modifierListOrNull() ?: return KtTokens.FINAL_KEYWORD
|
val modifiers = this.modifierListOrNull() ?: return Modality.FINAL
|
||||||
if (modifiers.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
|
if (modifiers.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
|
||||||
val klassModifiers = klass.modifierListOrNull()
|
val klassModifiers = klass.modifierListOrNull()
|
||||||
if (klassModifiers != null && klassModifiers.run {
|
if (klassModifiers != null && klassModifiers.run {
|
||||||
hasModifier(KtTokens.ABSTRACT_KEYWORD) || hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.SEALED_KEYWORD)
|
hasModifier(KtTokens.ABSTRACT_KEYWORD) || hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.SEALED_KEYWORD)
|
||||||
}) {
|
}) {
|
||||||
return KtTokens.OPEN_KEYWORD
|
return Modality.OPEN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,10 +269,10 @@ fun FirMemberDeclaration.implicitModality(context: CheckerContext): KtModifierKe
|
|||||||
&& klass.classKind == ClassKind.INTERFACE
|
&& klass.classKind == ClassKind.INTERFACE
|
||||||
&& !modifiers.hasModifier(KtTokens.PRIVATE_KEYWORD)
|
&& !modifiers.hasModifier(KtTokens.PRIVATE_KEYWORD)
|
||||||
) {
|
) {
|
||||||
return if (this.hasBody()) KtTokens.OPEN_KEYWORD else KtTokens.ABSTRACT_KEYWORD
|
return if (this.hasBody()) Modality.OPEN else Modality.ABSTRACT
|
||||||
}
|
}
|
||||||
|
|
||||||
return KtTokens.FINAL_KEYWORD
|
return Modality.FINAL
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FirDeclaration.modifierListOrNull() = (this.source.getModifierList() as? FirPsiModifierList)?.modifierList
|
private fun FirDeclaration.modifierListOrNull() = (this.source.getModifierList() as? FirPsiModifierList)?.modifierList
|
||||||
|
|||||||
+2
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
|
|
||||||
abstract class DeclarationCheckers {
|
abstract class DeclarationCheckers {
|
||||||
@@ -18,6 +19,7 @@ abstract class DeclarationCheckers {
|
|||||||
open val regularClassCheckers: List<FirRegularClassChecker> = emptyList()
|
open val regularClassCheckers: List<FirRegularClassChecker> = emptyList()
|
||||||
open val constructorCheckers: List<FirConstructorChecker> = emptyList()
|
open val constructorCheckers: List<FirConstructorChecker> = emptyList()
|
||||||
open val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = emptyList()
|
open val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = emptyList()
|
||||||
|
open val variableAssignmentCfaBasedCheckers: List<AbstractFirPropertyInitializationChecker> = emptyList()
|
||||||
|
|
||||||
internal val allFileCheckers: List<FirFileChecker> get() = fileCheckers + declarationCheckers
|
internal val allFileCheckers: List<FirFileChecker> get() = fileCheckers + declarationCheckers
|
||||||
internal val allMemberDeclarationCheckers: List<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + declarationCheckers
|
internal val allMemberDeclarationCheckers: List<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + declarationCheckers
|
||||||
|
|||||||
+5
-1
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.FirCallsEffectAnalyzer
|
import org.jetbrains.kotlin.fir.analysis.cfa.FirCallsEffectAnalyzer
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer
|
import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer
|
import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer
|
||||||
@@ -52,8 +53,11 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
|
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
|
||||||
FirPropertyInitializationAnalyzer,
|
|
||||||
FirCallsEffectAnalyzer,
|
FirCallsEffectAnalyzer,
|
||||||
FirReturnsImpliesAnalyzer
|
FirReturnsImpliesAnalyzer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val variableAssignmentCfaBasedCheckers: List<AbstractFirPropertyInitializationChecker> = listOf(
|
||||||
|
FirPropertyInitializationAnalyzer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
|
||||||
|
|
||||||
object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
||||||
@@ -20,7 +20,7 @@ object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
RedundantSetterParameterTypeChecker
|
RedundantSetterParameterTypeChecker
|
||||||
)
|
)
|
||||||
|
|
||||||
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
|
override val variableAssignmentCfaBasedCheckers: List<AbstractFirPropertyInitializationChecker> = listOf(
|
||||||
VariableAssignmentChecker
|
VariableAssignmentChecker
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+3
-3
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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>
|
||||||
|
|||||||
+3
-3
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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 -> {
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -15,15 +15,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 is FirClass<*>
|
it is FirClass<*>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInsideClass) {
|
if (!isInsideClass) {
|
||||||
reporter.report(functionCall.source)
|
reporter.report(expression.source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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) {
|
||||||
|
|||||||
+5
-5
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+14
-10
@@ -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
|
||||||
@@ -22,16 +22,19 @@ import org.jetbrains.kotlin.fir.types.coneType
|
|||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
|
||||||
|
|
||||||
object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVariableAssignment>() {
|
object CanBeReplacedWithOperatorAssignmentChecker : FirVariableAssignmentChecker() {
|
||||||
override fun check(functionCall: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(expression: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
val lValue = functionCall.lValue
|
val lValue = expression.lValue
|
||||||
if (lValue !is FirResolvedNamedReference) return
|
if (lValue !is FirResolvedNamedReference) return
|
||||||
val operator = functionCall.psi?.children?.getOrNull(1) ?: return
|
|
||||||
if (operator.text != "=") return
|
val operator = expression.psi?.children?.getOrNull(1) ?: return
|
||||||
|
val operationSign = (operator as? KtOperationReferenceExpression)?.operationSignTokenType
|
||||||
|
if (operationSign != KtTokens.EQ) return
|
||||||
|
|
||||||
val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return
|
val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return
|
||||||
val rValue = functionCall.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
|
||||||
|
|
||||||
@@ -56,9 +59,10 @@ object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVari
|
|||||||
|
|
||||||
val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
|
val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
|
||||||
&& leftExpression?.matcher(variable) == true
|
&& leftExpression?.matcher(variable) == true
|
||||||
val isRightMatch = isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
|
if (isLeftMatch) return true
|
||||||
&& rightExpression?.matcher(variable) == true
|
|
||||||
isLeftMatch or isRightMatch
|
return (isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
|
||||||
|
&& rightExpression?.matcher(variable) == true)
|
||||||
} else {
|
} else {
|
||||||
val leftExpression = left as? KtBinaryExpression
|
val leftExpression = left as? KtBinaryExpression
|
||||||
|
|
||||||
|
|||||||
+34
-9
@@ -10,19 +10,19 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
|||||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker
|
||||||
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.FirConstExpression
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||||
import org.jetbrains.kotlin.fir.psi
|
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 range = functionCall.psi ?: return
|
val left = expression.rangeLeft ?: return
|
||||||
val left = range.children.getOrNull(0)?.text?.toLongOrNull() ?: return
|
val right = expression.rangeRight ?: return
|
||||||
val right = range.children.getOrNull(2)?.text?.toLongOrNull() ?: return
|
|
||||||
|
|
||||||
val needReport = when (functionCall.calleeReference.name.asString()) {
|
val needReport = when (expression.calleeReference.name.asString()) {
|
||||||
"rangeTo" -> {
|
"rangeTo" -> {
|
||||||
left > right
|
left > right
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,34 @@ object EmptyRangeChecker : FirBasicExpresionChecker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needReport) {
|
if (needReport) {
|
||||||
reporter.report(functionCall.source, FirErrors.EMPTY_RANGE)
|
reporter.report(expression.source, FirErrors.EMPTY_RANGE)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val FirFunctionCall.rangeLeft: Long?
|
||||||
|
get() {
|
||||||
|
return if (explicitReceiver is FirIntegerOperatorCall) {
|
||||||
|
(explicitReceiver as? FirIntegerOperatorCall)?.asLong
|
||||||
|
} else {
|
||||||
|
(explicitReceiver as? FirConstExpression<*>)?.value as? Long
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FirFunctionCall.rangeRight: Long?
|
||||||
|
get() {
|
||||||
|
val arg = argumentList.arguments.getOrNull(0)
|
||||||
|
return if (arg is FirIntegerOperatorCall) arg.asLong
|
||||||
|
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?
|
||||||
|
get() {
|
||||||
|
val value = (dispatchReceiver as? FirConstExpression<*>)?.value as Long? ?: return null
|
||||||
|
if (this.calleeReference.name.asString() == "unaryMinus") {
|
||||||
|
return -value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+7
-7
@@ -26,18 +26,18 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val targetClassMap = mapOf(
|
private val targetClassMap = hashMapOf(
|
||||||
"toString" to StandardClassIds.String,
|
"toString" to StandardClassIds.String,
|
||||||
"toDouble" to StandardClassIds.Double,
|
"toDouble" to StandardClassIds.Double,
|
||||||
"toFloat" to StandardClassIds.Float,
|
"toFloat" to StandardClassIds.Float,
|
||||||
|
|||||||
+5
-6
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.extended
|
package org.jetbrains.kotlin.fir.analysis.checkers.extended
|
||||||
|
|
||||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.fir.FirFakeSourceElement
|
import org.jetbrains.kotlin.fir.FirFakeSourceElement
|
||||||
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.declaration.FirMemberDeclarationChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMemberDeclarationChecker
|
||||||
@@ -15,10 +15,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
|||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODALITY_MODIFIER
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODALITY_MODIFIER
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.modality
|
||||||
import org.jetbrains.kotlin.fir.psi
|
import org.jetbrains.kotlin.fir.psi
|
||||||
import org.jetbrains.kotlin.fir.toFirPsiSourceElement
|
import org.jetbrains.kotlin.fir.toFirPsiSourceElement
|
||||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
|
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
|
||||||
|
|
||||||
@@ -26,10 +25,9 @@ object RedundantModalityModifierChecker : FirMemberDeclarationChecker() {
|
|||||||
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (declaration.source is FirFakeSourceElement<*>) return
|
if (declaration.source is FirFakeSourceElement<*>) return
|
||||||
|
|
||||||
val modalityModifier = (declaration.psi as? KtDeclaration)?.modalityModifier() ?: return
|
val modality = declaration.modality ?: return
|
||||||
val modality = (modalityModifier as? LeafPsiElement)?.elementType as? KtModifierKeywordToken ?: return
|
|
||||||
if (
|
if (
|
||||||
modality == KtTokens.FINAL_KEYWORD
|
modality == Modality.FINAL
|
||||||
&& (context.containingDeclarations.last() as? FirClass<*>)?.classKind == ClassKind.INTERFACE
|
&& (context.containingDeclarations.last() as? FirClass<*>)?.classKind == ClassKind.INTERFACE
|
||||||
) return
|
) return
|
||||||
|
|
||||||
@@ -37,6 +35,7 @@ object RedundantModalityModifierChecker : FirMemberDeclarationChecker() {
|
|||||||
|
|
||||||
if (modality != implicitModality) return
|
if (modality != implicitModality) return
|
||||||
|
|
||||||
|
val modalityModifier = (declaration.psi as? KtDeclaration)?.modalityModifier() ?: return
|
||||||
reporter.report(modalityModifier.toFirPsiSourceElement(), REDUNDANT_MODALITY_MODIFIER)
|
reporter.report(modalityModifier.toFirPsiSourceElement(), REDUNDANT_MODALITY_MODIFIER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -19,10 +19,9 @@ object RedundantReturnUnitType : FirBasicDeclarationChecker() {
|
|||||||
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (declaration !is FirSimpleFunction) return
|
if (declaration !is FirSimpleFunction) return
|
||||||
if (declaration.body is FirSingleExpressionBlock) return
|
if (declaration.body is FirSingleExpressionBlock) return
|
||||||
if (declaration.returnTypeRef.source == null) return
|
|
||||||
if (declaration.source is FirFakeSourceElement<*>) return
|
|
||||||
|
|
||||||
val returnType = declaration.returnTypeRef
|
val returnType = declaration.returnTypeRef
|
||||||
|
if (returnType.source == null) return
|
||||||
|
if (declaration.source is FirFakeSourceElement<*>) return
|
||||||
if (returnType.annotations.isNotEmpty()) return
|
if (returnType.annotations.isNotEmpty()) return
|
||||||
|
|
||||||
if (returnType.isUnit) {
|
if (returnType.isUnit) {
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@ object RedundantSetterParameterTypeChecker : FirMemberDeclarationChecker() {
|
|||||||
val valueParameter = setter.valueParameters.firstOrNull() ?: return
|
val valueParameter = setter.valueParameters.firstOrNull() ?: return
|
||||||
val propertyTypeSource = declaration.returnTypeRef.source
|
val propertyTypeSource = declaration.returnTypeRef.source
|
||||||
val setterParameterTypeSource = valueParameter.returnTypeRef.source ?: return
|
val setterParameterTypeSource = valueParameter.returnTypeRef.source ?: return
|
||||||
|
|
||||||
if (setterParameterTypeSource != propertyTypeSource) {
|
if (setterParameterTypeSource != propertyTypeSource) {
|
||||||
reporter.report(setterParameterTypeSource, REDUNDANT_SETTER_PARAMETER_TYPE)
|
reporter.report(setterParameterTypeSource, REDUNDANT_SETTER_PARAMETER_TYPE)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -33,7 +33,6 @@ object RedundantSingleExpressionStringTemplateChecker : FirBasicExpresionChecker
|
|||||||
|
|
||||||
private fun PsiElement.findStringParent(): KtStringTemplateExpression? {
|
private fun PsiElement.findStringParent(): KtStringTemplateExpression? {
|
||||||
if (this is KtStringTemplateExpression) return this
|
if (this is KtStringTemplateExpression) return this
|
||||||
return if (this.parent != null) this.parent.findStringParent()
|
return this.parent?.findStringParent()
|
||||||
else null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -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
|
||||||
@@ -23,15 +24,16 @@ import org.jetbrains.kotlin.psi.KtProperty
|
|||||||
|
|
||||||
|
|
||||||
object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() {
|
object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() {
|
||||||
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
override fun analyze(
|
||||||
|
graph: ControlFlowGraph,
|
||||||
|
reporter: DiagnosticReporter,
|
||||||
|
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
||||||
|
properties: Set<FirPropertySymbol>
|
||||||
|
) {
|
||||||
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
||||||
val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>()
|
val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>()
|
||||||
|
|
||||||
val localProperties = LocalPropertyCollector.collect(graph)
|
val reporterVisitor = UninitializedPropertyReporter(data, properties, unprocessedProperties, propertiesCharacteristics)
|
||||||
if (localProperties.isEmpty()) return
|
|
||||||
|
|
||||||
val data = DataCollector(localProperties).getData(graph)
|
|
||||||
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, unprocessedProperties, propertiesCharacteristics)
|
|
||||||
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||||
|
|
||||||
for (property in unprocessedProperties) {
|
for (property in unprocessedProperties) {
|
||||||
|
|||||||
+4
-1
@@ -58,7 +58,10 @@ class ControlFlowAnalysisDiagnosticComponent(collector: AbstractDiagnosticCollec
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitPropertyAccessor(propertyAccessor: FirPropertyAccessor, data: CheckerContext) {
|
override fun visitPropertyAccessor(propertyAccessor: FirPropertyAccessor, data: CheckerContext) {
|
||||||
visitFunction(propertyAccessor, data)
|
val graph = propertyAccessor.controlFlowGraphReference?.controlFlowGraph ?: return
|
||||||
|
runCheck {
|
||||||
|
controlFlowAnalyzer.analyzePropertyAccessor(propertyAccessor, graph, data, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitConstructor(constructor: FirConstructor, data: CheckerContext) {
|
override fun visitConstructor(constructor: FirConstructor, data: CheckerContext) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ val o = object {
|
|||||||
p.x = 4
|
p.x = 4
|
||||||
|
|
||||||
val z : Int
|
val z : Int
|
||||||
doSmth(<!UNINITIALIZED_VARIABLE, UNINITIALIZED_VARIABLE!>z<!>)
|
doSmth(<!UNINITIALIZED_VARIABLE!>z<!>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user