[FIR] Don't report MUST_BE_INITIALIZED* on unreachable properties

This commit is contained in:
Andrey Zinovyev
2021-05-11 13:12:02 +03:00
committed by TeamCityServer
parent 8308f5d7d3
commit 84a7bdffe5
3 changed files with 87 additions and 15 deletions
@@ -87,7 +87,8 @@ internal fun checkPropertyInitializer(
modifierList: FirModifierList?,
isInitialized: Boolean,
reporter: DiagnosticReporter,
context: CheckerContext
context: CheckerContext,
reachable: Boolean = true
) {
val inInterface = containingClass?.isInterface == true
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
@@ -147,7 +148,7 @@ internal fun checkPropertyInitializer(
if (backingFieldRequired && !inInterface && !property.isLateInit && !isExpect && !isInitialized && !isExternal) {
if (property.receiverTypeRef != null && !property.hasAccessorImplementation) {
reporter.reportOn(propertySource, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context)
} else { // TODO: can be suppressed not to report diagnostics about no body
} else if (reachable) { // TODO: can be suppressed not to report diagnostics about no body
if (containingClass == null || property.hasAccessorImplementation) {
reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED, context)
} else {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
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.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfoCollector
@@ -20,6 +21,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.diagnostics.withSuppressedDiagnostics
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.expressions.FirExpression
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
@@ -40,13 +43,18 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() {
collectPropertyInitialization(declaration, memberPropertySymbols, initializedInConstructor, initializedInInitOrOtherProperty)
}
for (propertySymbol in memberPropertySymbols) {
val property = propertySymbol.fir
val isInitialized =
property.initializer != null ||
initializedInConstructor.getValue(propertySymbol).isDefinitelyVisited() ||
initializedInInitOrOtherProperty.getValue(propertySymbol).isDefinitelyVisited()
checkProperty(declaration, property, isInitialized, context, reporter)
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()
checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd)
}
reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration)
}
}
@@ -140,7 +148,8 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() {
property: FirProperty,
isInitialized: Boolean,
context: CheckerContext,
reporter: DiagnosticReporter
reporter: DiagnosticReporter,
reachable: Boolean
) {
val source = property.source ?: return
if (source.kind is FirFakeSourceElementKind) return
@@ -155,7 +164,8 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() {
modifierList,
isInitialized,
reporter,
context
context,
reachable
)
checkExpectDeclarationVisibilityAndBody(property, source, reporter, context)
@@ -205,4 +215,27 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() {
}
}
}
private fun FirRegularClass.collectDeadEndDeclarations(): Set<FirElement> {
val cfg = controlFlowGraphReference?.controlFlowGraph ?: return emptySet()
return cfg.exitNode.incomingEdges.keys
.map { it.fir }
.filter { it.isDeadEnd() }
.toSet()
}
/**
* The idea is to find presumably exit node is dead one
* 1. in the case of block expression, it is BlockExitNode
* 2. in other cases treat any dead node that leads to exitNode as evidence of deadness
* This is all a workaround because ideally exit node itself should be dead if it is unreachable
*/
private fun FirElement.isDeadEnd(): Boolean {
val cfg = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return false
cfg.exitNode.incomingEdges.keys.find { it is BlockExitNode }?.let {
return it.isDead
}
return cfg.exitNode.incomingEdges.keys.any { it.isDead }
}
}
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: EXCEPTIONS_NOT_IMPLEMENTED
// WITH_RUNTIME
@@ -12,10 +11,49 @@ class C {
var uninitializedVar: String
}
fun box(): String =
class Foo {
init {
TODO()
}
val uninitializedVal: String
var uninitializedVar: String
}
class Bar {
val initializedVal = 43
init {
TODO()
}
val uninitializedVal: String
var uninitializedVar: String
}
fun box(): String {
try {
C()
"Fail"
return "Fail"
} catch (e: NotImplementedError) {
"OK"
//OK
}
try {
Foo()
return "Fail"
} catch (e: NotImplementedError) {
//OK
}
try {
Bar()
return "Fail"
} catch (e: NotImplementedError) {
//OK
}
return "OK"
}