From d054617b8ef465bba9174e88586a7ba6a51a2554 Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 28 Sep 2022 14:36:47 +0200 Subject: [PATCH] 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). --- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 9 +++++++-- .../dfa/FirLocalVariableAssignmentAnalyzer.kt | 17 ++++++++--------- .../fir/resolve/dfa/PreliminaryLoopVisitor.kt | 4 ++++ .../smartCasts/implicitThisOrLocalVar.fir.kt | 4 ++-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index bc4dd31022b..504fca960bb 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -1216,9 +1216,14 @@ abstract class FirDataFlowAnalyzer( 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) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt index 8f83e10f155..8f899738464 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt @@ -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) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PreliminaryLoopVisitor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PreliminaryLoopVisitor.kt index 9bdbd01cf08..bfeb29ab161 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PreliminaryLoopVisitor.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/PreliminaryLoopVisitor.kt @@ -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) diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitThisOrLocalVar.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/implicitThisOrLocalVar.fir.kt index 6bbb354caf5..7b58f63e906 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/implicitThisOrLocalVar.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitThisOrLocalVar.fir.kt @@ -10,7 +10,7 @@ fun Box.test() { other.item = null take(item) this.item = null - take(item) + take(item) } } @@ -18,7 +18,7 @@ fun Box.test() { myRun { if (item != null) { this.item = null - take(item) + take(item) } } }