FIR DFA: slightly refactor smart cast state tracking

Instead of always looking up smart casts on `lastNode` and separately
tracking the receiver state, simply track which flow the smart casts
belong to right now. This tracked flow is auto-advanced when new
`lastNode`s are created, but can be manually rolled back for things like
KT-63709.
This commit is contained in:
Sonya Valchuk
2024-02-16 11:25:22 +00:00
committed by Space Cloud
parent 052baf1b04
commit 4c8febf10d
5 changed files with 39 additions and 50 deletions
@@ -58,10 +58,7 @@ internal open class StubBodyResolveTransformerComponents(
override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) =
error("Should not be called")
override fun getTypeUsingSmartcastInfo(
expression: FirExpression,
ignoreCallArguments: Boolean,
): Pair<PropertyStability, MutableList<ConeKotlinType>>? =
override fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? =
null
}
}
@@ -366,9 +366,8 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: FirBasedSymbol<*>): Fir
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
qualifiedAccessExpression: FirQualifiedAccessExpression,
ignoreCallArguments: Boolean,
): FirExpression {
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression, ignoreCallArguments)
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
?: return qualifiedAccessExpression
return transformExpressionUsingSmartcastInfo(qualifiedAccessExpression, stability, typesFromSmartCast) ?: qualifiedAccessExpression
@@ -377,7 +376,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo(
whenSubjectExpression: FirWhenSubjectExpression,
): FirExpression {
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(whenSubjectExpression, ignoreCallArguments = false)
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(whenSubjectExpression)
?: return whenSubjectExpression
return transformExpressionUsingSmartcastInfo(whenSubjectExpression, stability, typesFromSmartCast) ?: whenSubjectExpression
@@ -387,7 +386,7 @@ fun BodyResolveComponents.transformDesugaredAssignmentValueUsingSmartcastInfo(
expression: FirDesugaredAssignmentValueReferenceExpression,
): FirExpression {
val (stability, typesFromSmartCast) =
dataFlowAnalyzer.getTypeUsingSmartcastInfo(expression.expressionRef.value, ignoreCallArguments = false)
dataFlowAnalyzer.getTypeUsingSmartcastInfo(expression.expressionRef.value)
?: return expression
return transformExpressionUsingSmartcastInfo(expression, stability, typesFromSmartCast) ?: expression
@@ -344,7 +344,7 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable(
}.build().let {
callCompleter.completeCall(it, ResolutionMode.ReceiverResolution)
}.let {
transformQualifiedAccessUsingSmartcastInfo(it, ignoreCallArguments = true)
transformQualifiedAccessUsingSmartcastInfo(it)
}
}
@@ -161,17 +161,9 @@ abstract class FirDataFlowAnalyzer(
* is **stateful** and changes as the FIR tree is navigated by [FirDataFlowAnalyzer].
*
* @param expression The variable access expression.
* @param ignoreCallArguments Should be set to `true` when call argument flow should not be used for smart-casting. This is important
* because the receiver of implicit `invoke` calls is visited *after* the call arguments due to tower resolution.
*/
open fun getTypeUsingSmartcastInfo(
expression: FirExpression,
ignoreCallArguments: Boolean,
): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
// TODO(KT-64094): Consider moving logic to tower resolution instead.
val node = graphBuilder.lastNode
.let { if (ignoreCallArguments && it is FunctionCallArgumentsExitNode) it.enterNode else it }
val flow = node.flow
open fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
val flow = currentSmartCastPosition ?: return null
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(flow, expression) ?: return null
val types = flow.getTypeStatement(variable)?.exactType?.ifEmpty { null } ?: return null
return variable.stability to types.toMutableList()
@@ -221,7 +213,7 @@ abstract class FirDataFlowAnalyzer(
val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(function)
functionExitNode.mergeIncomingFlow()
postponedLambdaExitNode?.mergeIncomingFlow()
resetReceivers() // roll back to state before function
resetSmartCastPosition() // roll back to state before function
return FirControlFlowGraphReferenceImpl(graph)
}
@@ -234,7 +226,7 @@ abstract class FirDataFlowAnalyzer(
}
}
val info = DataFlowInfo(variableStorage)
resetReceivers()
resetSmartCastPosition()
return FirControlFlowGraphReferenceImpl(graph, info)
}
@@ -255,7 +247,7 @@ abstract class FirDataFlowAnalyzer(
if (node != null) {
node.mergeIncomingFlow()
} else {
resetReceivers()
resetSmartCastPosition()
}
graph?.completePostponedNodes()
return graph
@@ -276,7 +268,7 @@ abstract class FirDataFlowAnalyzer(
if (node != null) {
node.mergeIncomingFlow()
} else {
resetReceivers() // to state before class initialization
resetSmartCastPosition() // to state before class initialization
}
graph?.completePostponedNodes()
return graph
@@ -932,9 +924,7 @@ abstract class FirDataFlowAnalyzer(
// Reset implicit receivers back to their state *before* call arguments as tower resolve will use receiver types to lookup
// functions after call arguments have been processed.
// TODO(KT-64094): Consider moving logic to tower resolution instead.
val flow = exitNode.enterNode.flow
updateAllReceivers(currentReceiverState, flow)
currentReceiverState = flow
resetSmartCastPositionTo(exitNode.enterNode.flow)
}
}
@@ -1338,12 +1328,10 @@ abstract class FirDataFlowAnalyzer(
// ------------------------------------------------------ Utils ------------------------------------------------------
// Smart cast information is taken from `graphBuilder.lastNode`, but the problem with receivers specifically
// is that they also affect tower resolver's scope stack. To allow accessing members on smart casted receivers,
// we explicitly patch up the stack by calling `receiverUpdated` in a way that maintains consistency with
// `getTypeUsingSmartcastInfo`; i.e. at any point between calls to this class' methods the types in the implicit
// receiver stack also correspond to the data flow information attached to `graphBuilder.lastNode`.
private var currentReceiverState: Flow? = null
// The data flow state from which type statements are taken during expression resolution.
// Should normally be equal to `graphBuilder.lastNode`, but one exception is between exiting call
// arguments and exiting the call itself, where smart casting does not use information from the arguments.
private var currentSmartCastPosition: Flow? = null
private fun CFGNode<*>.buildDefaultFlow(
builder: (FlowPath, MutableFlow) -> Unit,
@@ -1375,12 +1363,13 @@ abstract class FirDataFlowAnalyzer(
val result = logicSystem.joinFlow(previousFlows, statementFlows, isUnion)
if (graphBuilder.lastNodeOrNull == this) {
// Here it is, the new `lastNode`. If the previous state is the only predecessor, then there is actually
// nothing to update; `addTypeStatement` has already ensured we have the correct information.
if (currentReceiverState == null || previousFlows.singleOrNull() != currentReceiverState) {
updateAllReceivers(currentReceiverState, result)
if (currentSmartCastPosition == null || currentSmartCastPosition != previousFlows.singleOrNull()) {
// Force-update the receiver stack as merging multiple flows might have changed receivers' type statements.
resetSmartCastPositionTo(result)
} else {
// Receiver stack should already be up-to-date, only need to swap the flow for explicit lookups.
currentSmartCastPosition = result
}
currentReceiverState = result
}
builder(FlowPath.Default, result)
@@ -1431,8 +1420,8 @@ abstract class FirDataFlowAnalyzer(
// Always build the default flow path for all nodes.
val mutableDefaultFlow = buildDefaultFlow(builder)
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
if (currentReceiverState === mutableDefaultFlow) {
currentReceiverState = defaultFlow
if (currentSmartCastPosition === mutableDefaultFlow) {
currentSmartCastPosition = defaultFlow
}
// Propagate alternate flows from previous nodes.
@@ -1498,23 +1487,27 @@ abstract class FirDataFlowAnalyzer(
// In rare cases (like after exiting functions) after adding more nodes `graphBuilder` will revert the current
// state to a previously created node, so none of the nodes it returned are `lastNode` and `mergeIncomingFlow`
// will not ensure consistency. In that case an explicit call to `resetReceivers` is needed to roll back the stack
// to that previously created node's state.
private fun resetReceivers() {
val currentFlow = graphBuilder.lastNodeOrNull?.flow
updateAllReceivers(currentReceiverState, currentFlow)
currentReceiverState = currentFlow
// will not ensure the smart cast position is auto-advanced. In that case an explicit call to `resetSmartCastPosition`
// is needed to roll back to that previously created node's state.
private fun resetSmartCastPosition() {
resetSmartCastPositionTo(graphBuilder.lastNodeOrNull?.flow)
}
private fun updateAllReceivers(from: Flow?, to: Flow?) {
// This method can be used to change the smart cast state to some node that is not the one at which the graph
// builder is currently stopped. This is temporary: adding any more nodes to the graph will restart tracking
// of the current position in the graph.
private fun resetSmartCastPositionTo(flow: Flow?) {
val previous = currentSmartCastPosition
if (previous == flow) return
receiverStack.forEach {
variableStorage.getLocalVariable(it.boundSymbol)?.let { variable ->
val newStatement = to?.getTypeStatement(variable)
if (newStatement != from?.getTypeStatement(variable)) {
val newStatement = flow?.getTypeStatement(variable)
if (newStatement != previous?.getTypeStatement(variable)) {
receiverUpdated(it.boundSymbol, newStatement)
}
}
}
currentSmartCastPosition = flow
}
private fun isSameValueIn(other: PersistentFlow, fir: FirElement, original: MutableFlow): Boolean {
@@ -1528,7 +1521,7 @@ abstract class FirDataFlowAnalyzer(
private fun MutableFlow.addTypeStatement(info: TypeStatement) {
val newStatement = logicSystem.addTypeStatement(this, info) ?: return
if (newStatement.variable.isThisReference && this === currentReceiverState) {
if (newStatement.variable.isThisReference && this === currentSmartCastPosition) {
receiverUpdated(newStatement.variable.identifier.symbol, newStatement)
}
}
@@ -177,7 +177,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
when (result) {
is FirQualifiedAccessExpression -> {
dataFlowAnalyzer.exitQualifiedAccessExpression(result)
result = components.transformQualifiedAccessUsingSmartcastInfo(result, ignoreCallArguments = false)
result = components.transformQualifiedAccessUsingSmartcastInfo(result)
if (result is FirSmartCastExpression) {
dataFlowAnalyzer.exitSmartCastExpression(result)
}