FIR CFG: handle reassignments with explicit receivers more precisely

When a looping control structure has a reassignment with an explicit
receiver, we don't need to erase its smartcast info as it has to be a
non-local property and is thus automatically unstable anyway. However,
we should still do that upon reaching the assignment itself for slightly
more precise error messages (as the property not just "could be"
reassigned, but *has been* reassigned already).
This commit is contained in:
pyos
2022-09-28 14:36:47 +02:00
committed by teamcity
parent 77c2601382
commit d054617b8e
4 changed files with 21 additions and 13 deletions
@@ -1216,9 +1216,14 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitVariableAssignment(assignment: FirVariableAssignment) {
val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
val property = assignment.lValue.resolvedSymbol?.fir as? FirProperty ?: return
// TODO: add unstable smartcast
if (property.isLocal || !property.isVar) {
if (property.isLocal || property.isVal) {
exitVariableInitialization(node, assignment.rValue, property, assignment, hasExplicitType = false)
} else {
// TODO: add unstable smartcast for non-local var
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(node.flow, property.symbol, assignment)
if (variable != null) {
logicSystem.removeAllAboutVariable(node.flow, variable)
}
}
processConditionalContract(assignment)
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.referredPropertySymbol
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.fir.resolve.dfa.FirLocalVariableAssignmentAnalyzer.Companion.MiniFlow.Companion.join
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.visitors.FirVisitor
@@ -369,23 +370,21 @@ internal class FirLocalVariableAssignmentAnalyzer(
override fun visitVariableAssignment(variableAssignment: FirVariableAssignment, data: MiniCfgData) {
super.visitVariableAssignment(variableAssignment, data)
val flow = data.flow ?: return
val name = (variableAssignment.lValue as? FirNamedReference)?.name ?: return
flow.recordAssignment(name, data)
if (variableAssignment.explicitReceiver != null) return
data.recordAssignment(variableAssignment.lValue)
}
override fun visitAssignmentOperatorStatement(assignmentOperatorStatement: FirAssignmentOperatorStatement, data: MiniCfgData) {
super.visitAssignmentOperatorStatement(assignmentOperatorStatement, data)
val flow = data.flow ?: return
val lhs = assignmentOperatorStatement.leftArgument as? FirQualifiedAccessExpression ?: return
if (lhs.explicitReceiver != null) return
val name = (lhs.calleeReference as? FirNamedReference)?.name ?: return
flow.recordAssignment(name, data)
data.recordAssignment(lhs.calleeReference)
}
fun MiniFlow.recordAssignment(name: Name, data: MiniCfgData) {
val property = data.resolveLocalVariable(name) ?: return
recordAssignment(property, mutableSetOf())
fun MiniCfgData.recordAssignment(reference: FirReference) {
val name = (reference as? FirNamedReference)?.name ?: return
val property = resolveLocalVariable(name) ?: return
flow?.recordAssignment(property, mutableSetOf())
}
private fun MiniFlow.recordAssignment(property: FirProperty, visited: MutableSet<MiniFlow>) {
@@ -44,6 +44,10 @@ class PreliminaryLoopVisitor {
override fun visitVariableAssignment(variableAssignment: FirVariableAssignment, data: FirStatement?) {
if (variableAssignment.source?.kind == KtFakeSourceElementKind.DesugaredIncrementOrDecrement) return
// Only care about local variable assignments, which never have explicit receivers. If this is a `var`
// property assignment, the smart cast will be unstable anyway.
if (variableAssignment.explicitReceiver != null) return
val reference = variableAssignment.lValue as? FirNamedReference
if (reference != null) {
requireNotNull(data)
@@ -10,7 +10,7 @@ fun Box.test() {
other.item = null
take<String>(<!SMARTCAST_IMPOSSIBLE!>item<!>)
this.item = null
take<String>(<!SMARTCAST_IMPOSSIBLE!>item<!>)
take<String>(<!ARGUMENT_TYPE_MISMATCH!>item<!>)
}
}
@@ -18,7 +18,7 @@ fun Box.test() {
myRun {
if (item != null) {
this.item = null
take<String>(<!SMARTCAST_IMPOSSIBLE!>item<!>)
take<String>(item)
}
}
}