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:
+7
-2
@@ -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)
|
||||
}
|
||||
|
||||
+8
-9
@@ -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>) {
|
||||
|
||||
+4
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user