FIR: simplify FirMemberPropertiesChecker
Now that there are correct control flow edges between constructors, collecting the data is as simple as going over the entire initialization graph.
This commit is contained in:
+9
-112
@@ -6,11 +6,9 @@
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfo
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoCollector
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
||||
@@ -23,135 +21,34 @@ import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
// See old FE's [DeclarationsChecker]
|
||||
object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val memberPropertySymbols = declaration.declarations.filterIsInstance<FirProperty>().map { it.symbol }.toSet()
|
||||
val initializedInConstructor =
|
||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
||||
val initializedInInitOrOtherProperty =
|
||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
||||
|
||||
// If all member properties have its own initializer, we don't need to collect property initialization info at all.
|
||||
if (memberPropertySymbols.any { !it.hasInitializer }) {
|
||||
collectPropertyInitialization(
|
||||
declaration,
|
||||
context.session,
|
||||
memberPropertySymbols,
|
||||
initializedInConstructor,
|
||||
initializedInInitOrOtherProperty
|
||||
)
|
||||
}
|
||||
|
||||
val info = declaration.collectInitializationInfo()
|
||||
val deadEnds = declaration.collectDeadEndDeclarations()
|
||||
var reachedDeadEnd = false
|
||||
for (innerDeclaration in declaration.declarations) {
|
||||
if (innerDeclaration is FirProperty) {
|
||||
val symbol = innerDeclaration.symbol
|
||||
val isInitialized =
|
||||
innerDeclaration.initializer != null ||
|
||||
initializedInConstructor.getValue(symbol).isDefinitelyVisited() ||
|
||||
initializedInInitOrOtherProperty.getValue(symbol).isDefinitelyVisited()
|
||||
val isInitialized = innerDeclaration.initializer != null || info?.get(symbol)?.isDefinitelyVisited() == true
|
||||
checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd)
|
||||
}
|
||||
reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectPropertyInitialization(
|
||||
klass: FirClass,
|
||||
session: FirSession,
|
||||
memberPropertySymbols: Set<FirPropertySymbol>,
|
||||
initializedInConstructor: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
||||
initializedInInitOrOtherProperty: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
||||
) {
|
||||
// A property is known to be initialized only if it is initialized
|
||||
// 1) with its own initializing expression;
|
||||
// 2) at every class constructor;
|
||||
// 3) at any of class's anonymous initializers; or
|
||||
// 4) at other property's initializing expression
|
||||
|
||||
// 2) Property can be initialized at constructors. Since it's unknown what constructor will be used, the property can be determined
|
||||
// as initialized only if it is initialized at every constructor. We should consider a delegated constructor, e.g.,
|
||||
// constructor() { x = ... }
|
||||
// constructor(...): this() { ... } // x will be initialized via this() delegation
|
||||
// We need to topologically sort constructors so that we can process delegated ones before the use sites.
|
||||
|
||||
// 3) Property can be initialized at any of class's anonymous initializers (all of initializers will be executed), e.g.,
|
||||
// init { x = ... }
|
||||
// ...
|
||||
// init { y = ... }
|
||||
|
||||
// 4) Property can be initialized at other property's initializing expression too, e.g.,
|
||||
// val initX = inlineMe { x = ... } // where inlineMe returns the value of the last expression of the lambda
|
||||
|
||||
// To handle the delegated constructor call, we need a cache from constructor to (analyzed) property init info.
|
||||
val constructorToData =
|
||||
mutableMapOf<FirConstructorSymbol, PropertyInitializationInfo>().withDefault { PropertyInitializationInfo.EMPTY }
|
||||
|
||||
fun collectInfoFromGraph(
|
||||
graph: ControlFlowGraph,
|
||||
map: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
||||
acc: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange,
|
||||
delegatedConstructor: FirConstructorSymbol? = null,
|
||||
) {
|
||||
val delegatedInfo = delegatedConstructor?.let { constructorToData.getValue(it) } ?: PropertyInitializationInfo.EMPTY
|
||||
|
||||
val data = PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)
|
||||
val infoAtExitNode = data[graph.exitNode]?.get(NormalPath) ?: PropertyInitializationInfo.EMPTY
|
||||
|
||||
// NB: it's not [merge], which is conducted at merging points, such as loop condition or when conditions.
|
||||
// Rather, delegated constructor call is the predecessor of the current constructor call, so we should accumulate.
|
||||
val info = delegatedInfo.plus(infoAtExitNode)
|
||||
|
||||
if (graph.declaration is FirConstructor) {
|
||||
constructorToData.putIfAbsent((graph.declaration as FirConstructor).symbol, info)
|
||||
}
|
||||
|
||||
for (propertySymbol in memberPropertySymbols) {
|
||||
val item = map[propertySymbol]
|
||||
if (item != null) {
|
||||
// Accumulation:
|
||||
// range join for class constructors, range plus for class's anonymous initializers and property initializations
|
||||
map[propertySymbol] = acc.invoke(item, info[propertySymbol] ?: EventOccurrencesRange.ZERO)
|
||||
} else {
|
||||
// Initial assignment.
|
||||
// NB: we should not use `acc` here to not weaken ranges. For example, if we visit one and only constructor where
|
||||
// a property of interest is correctly initialized (a.k.a. [EXACTLY_ONCE]), and if `acc` is ...Range::or,
|
||||
// merging with the default [ZERO] makes the result [AT_MOST_ONCE], which will be regarded as uninitialized.
|
||||
map[propertySymbol] = info[propertySymbol] ?: EventOccurrencesRange.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val constructorGraphs = klass.constructorsSortedByDelegation(session).mapNotNull {
|
||||
it.resolvedControlFlowGraphReference?.controlFlowGraph
|
||||
}
|
||||
for (graph in constructorGraphs) {
|
||||
collectInfoFromGraph(
|
||||
graph,
|
||||
initializedInConstructor,
|
||||
EventOccurrencesRange::or,
|
||||
(graph.declaration as? FirConstructor)?.symbol?.delegatedThisConstructor
|
||||
)
|
||||
}
|
||||
|
||||
val initGraphs = klass.anonymousInitializers.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
||||
for (graph in initGraphs) {
|
||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
||||
}
|
||||
|
||||
val propertyInitGraphs = memberPropertySymbols.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
||||
for (graph in propertyInitGraphs) {
|
||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
||||
private fun FirClass.collectInitializationInfo(): PropertyInitializationInfo? {
|
||||
val graph = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return null
|
||||
val memberPropertySymbols = declarations.mapNotNullTo(mutableSetOf()) {
|
||||
(it as? FirProperty)?.takeIf { fir -> fir.initializer == null }?.symbol
|
||||
}
|
||||
if (memberPropertySymbols.isEmpty()) return null
|
||||
// TODO: this also visits non-constructor member functions...
|
||||
return PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)[graph.exitNode]?.get(NormalPath)
|
||||
}
|
||||
|
||||
private fun checkProperty(
|
||||
|
||||
Reference in New Issue
Block a user