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:
committed by
Space Cloud
parent
052baf1b04
commit
4c8febf10d
+1
-4
@@ -58,10 +58,7 @@ internal open class StubBodyResolveTransformerComponents(
|
|||||||
override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) =
|
override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) =
|
||||||
error("Should not be called")
|
error("Should not be called")
|
||||||
|
|
||||||
override fun getTypeUsingSmartcastInfo(
|
override fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? =
|
||||||
expression: FirExpression,
|
|
||||||
ignoreCallArguments: Boolean,
|
|
||||||
): Pair<PropertyStability, MutableList<ConeKotlinType>>? =
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,9 +366,8 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: FirBasedSymbol<*>): Fir
|
|||||||
|
|
||||||
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||||
qualifiedAccessExpression: FirQualifiedAccessExpression,
|
qualifiedAccessExpression: FirQualifiedAccessExpression,
|
||||||
ignoreCallArguments: Boolean,
|
|
||||||
): FirExpression {
|
): FirExpression {
|
||||||
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression, ignoreCallArguments)
|
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
|
||||||
?: return qualifiedAccessExpression
|
?: return qualifiedAccessExpression
|
||||||
|
|
||||||
return transformExpressionUsingSmartcastInfo(qualifiedAccessExpression, stability, typesFromSmartCast) ?: qualifiedAccessExpression
|
return transformExpressionUsingSmartcastInfo(qualifiedAccessExpression, stability, typesFromSmartCast) ?: qualifiedAccessExpression
|
||||||
@@ -377,7 +376,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
|||||||
fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo(
|
fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo(
|
||||||
whenSubjectExpression: FirWhenSubjectExpression,
|
whenSubjectExpression: FirWhenSubjectExpression,
|
||||||
): FirExpression {
|
): FirExpression {
|
||||||
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(whenSubjectExpression, ignoreCallArguments = false)
|
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(whenSubjectExpression)
|
||||||
?: return whenSubjectExpression
|
?: return whenSubjectExpression
|
||||||
|
|
||||||
return transformExpressionUsingSmartcastInfo(whenSubjectExpression, stability, typesFromSmartCast) ?: whenSubjectExpression
|
return transformExpressionUsingSmartcastInfo(whenSubjectExpression, stability, typesFromSmartCast) ?: whenSubjectExpression
|
||||||
@@ -387,7 +386,7 @@ fun BodyResolveComponents.transformDesugaredAssignmentValueUsingSmartcastInfo(
|
|||||||
expression: FirDesugaredAssignmentValueReferenceExpression,
|
expression: FirDesugaredAssignmentValueReferenceExpression,
|
||||||
): FirExpression {
|
): FirExpression {
|
||||||
val (stability, typesFromSmartCast) =
|
val (stability, typesFromSmartCast) =
|
||||||
dataFlowAnalyzer.getTypeUsingSmartcastInfo(expression.expressionRef.value, ignoreCallArguments = false)
|
dataFlowAnalyzer.getTypeUsingSmartcastInfo(expression.expressionRef.value)
|
||||||
?: return expression
|
?: return expression
|
||||||
|
|
||||||
return transformExpressionUsingSmartcastInfo(expression, stability, typesFromSmartCast) ?: expression
|
return transformExpressionUsingSmartcastInfo(expression, stability, typesFromSmartCast) ?: expression
|
||||||
|
|||||||
+1
-1
@@ -344,7 +344,7 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable(
|
|||||||
}.build().let {
|
}.build().let {
|
||||||
callCompleter.completeCall(it, ResolutionMode.ReceiverResolution)
|
callCompleter.completeCall(it, ResolutionMode.ReceiverResolution)
|
||||||
}.let {
|
}.let {
|
||||||
transformQualifiedAccessUsingSmartcastInfo(it, ignoreCallArguments = true)
|
transformQualifiedAccessUsingSmartcastInfo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-40
@@ -161,17 +161,9 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
* is **stateful** and changes as the FIR tree is navigated by [FirDataFlowAnalyzer].
|
* is **stateful** and changes as the FIR tree is navigated by [FirDataFlowAnalyzer].
|
||||||
*
|
*
|
||||||
* @param expression The variable access expression.
|
* @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(
|
open fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
||||||
expression: FirExpression,
|
val flow = currentSmartCastPosition ?: return null
|
||||||
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
|
|
||||||
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(flow, expression) ?: return null
|
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(flow, expression) ?: return null
|
||||||
val types = flow.getTypeStatement(variable)?.exactType?.ifEmpty { null } ?: return null
|
val types = flow.getTypeStatement(variable)?.exactType?.ifEmpty { null } ?: return null
|
||||||
return variable.stability to types.toMutableList()
|
return variable.stability to types.toMutableList()
|
||||||
@@ -221,7 +213,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(function)
|
val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(function)
|
||||||
functionExitNode.mergeIncomingFlow()
|
functionExitNode.mergeIncomingFlow()
|
||||||
postponedLambdaExitNode?.mergeIncomingFlow()
|
postponedLambdaExitNode?.mergeIncomingFlow()
|
||||||
resetReceivers() // roll back to state before function
|
resetSmartCastPosition() // roll back to state before function
|
||||||
return FirControlFlowGraphReferenceImpl(graph)
|
return FirControlFlowGraphReferenceImpl(graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +226,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val info = DataFlowInfo(variableStorage)
|
val info = DataFlowInfo(variableStorage)
|
||||||
resetReceivers()
|
resetSmartCastPosition()
|
||||||
return FirControlFlowGraphReferenceImpl(graph, info)
|
return FirControlFlowGraphReferenceImpl(graph, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +247,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
if (node != null) {
|
if (node != null) {
|
||||||
node.mergeIncomingFlow()
|
node.mergeIncomingFlow()
|
||||||
} else {
|
} else {
|
||||||
resetReceivers()
|
resetSmartCastPosition()
|
||||||
}
|
}
|
||||||
graph?.completePostponedNodes()
|
graph?.completePostponedNodes()
|
||||||
return graph
|
return graph
|
||||||
@@ -276,7 +268,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
if (node != null) {
|
if (node != null) {
|
||||||
node.mergeIncomingFlow()
|
node.mergeIncomingFlow()
|
||||||
} else {
|
} else {
|
||||||
resetReceivers() // to state before class initialization
|
resetSmartCastPosition() // to state before class initialization
|
||||||
}
|
}
|
||||||
graph?.completePostponedNodes()
|
graph?.completePostponedNodes()
|
||||||
return graph
|
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
|
// 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.
|
// functions after call arguments have been processed.
|
||||||
// TODO(KT-64094): Consider moving logic to tower resolution instead.
|
// TODO(KT-64094): Consider moving logic to tower resolution instead.
|
||||||
val flow = exitNode.enterNode.flow
|
resetSmartCastPositionTo(exitNode.enterNode.flow)
|
||||||
updateAllReceivers(currentReceiverState, flow)
|
|
||||||
currentReceiverState = flow
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1338,12 +1328,10 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
|
|
||||||
// ------------------------------------------------------ Utils ------------------------------------------------------
|
// ------------------------------------------------------ Utils ------------------------------------------------------
|
||||||
|
|
||||||
// Smart cast information is taken from `graphBuilder.lastNode`, but the problem with receivers specifically
|
// The data flow state from which type statements are taken during expression resolution.
|
||||||
// is that they also affect tower resolver's scope stack. To allow accessing members on smart casted receivers,
|
// Should normally be equal to `graphBuilder.lastNode`, but one exception is between exiting call
|
||||||
// we explicitly patch up the stack by calling `receiverUpdated` in a way that maintains consistency with
|
// arguments and exiting the call itself, where smart casting does not use information from the arguments.
|
||||||
// `getTypeUsingSmartcastInfo`; i.e. at any point between calls to this class' methods the types in the implicit
|
private var currentSmartCastPosition: Flow? = null
|
||||||
// receiver stack also correspond to the data flow information attached to `graphBuilder.lastNode`.
|
|
||||||
private var currentReceiverState: Flow? = null
|
|
||||||
|
|
||||||
private fun CFGNode<*>.buildDefaultFlow(
|
private fun CFGNode<*>.buildDefaultFlow(
|
||||||
builder: (FlowPath, MutableFlow) -> Unit,
|
builder: (FlowPath, MutableFlow) -> Unit,
|
||||||
@@ -1375,12 +1363,13 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
val result = logicSystem.joinFlow(previousFlows, statementFlows, isUnion)
|
val result = logicSystem.joinFlow(previousFlows, statementFlows, isUnion)
|
||||||
|
|
||||||
if (graphBuilder.lastNodeOrNull == this) {
|
if (graphBuilder.lastNodeOrNull == this) {
|
||||||
// Here it is, the new `lastNode`. If the previous state is the only predecessor, then there is actually
|
if (currentSmartCastPosition == null || currentSmartCastPosition != previousFlows.singleOrNull()) {
|
||||||
// nothing to update; `addTypeStatement` has already ensured we have the correct information.
|
// Force-update the receiver stack as merging multiple flows might have changed receivers' type statements.
|
||||||
if (currentReceiverState == null || previousFlows.singleOrNull() != currentReceiverState) {
|
resetSmartCastPositionTo(result)
|
||||||
updateAllReceivers(currentReceiverState, 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)
|
builder(FlowPath.Default, result)
|
||||||
@@ -1431,8 +1420,8 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
// Always build the default flow path for all nodes.
|
// Always build the default flow path for all nodes.
|
||||||
val mutableDefaultFlow = buildDefaultFlow(builder)
|
val mutableDefaultFlow = buildDefaultFlow(builder)
|
||||||
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
|
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
|
||||||
if (currentReceiverState === mutableDefaultFlow) {
|
if (currentSmartCastPosition === mutableDefaultFlow) {
|
||||||
currentReceiverState = defaultFlow
|
currentSmartCastPosition = defaultFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate alternate flows from previous nodes.
|
// 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
|
// 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`
|
// 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
|
// will not ensure the smart cast position is auto-advanced. In that case an explicit call to `resetSmartCastPosition`
|
||||||
// to that previously created node's state.
|
// is needed to roll back to that previously created node's state.
|
||||||
private fun resetReceivers() {
|
private fun resetSmartCastPosition() {
|
||||||
val currentFlow = graphBuilder.lastNodeOrNull?.flow
|
resetSmartCastPositionTo(graphBuilder.lastNodeOrNull?.flow)
|
||||||
updateAllReceivers(currentReceiverState, currentFlow)
|
|
||||||
currentReceiverState = currentFlow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
receiverStack.forEach {
|
||||||
variableStorage.getLocalVariable(it.boundSymbol)?.let { variable ->
|
variableStorage.getLocalVariable(it.boundSymbol)?.let { variable ->
|
||||||
val newStatement = to?.getTypeStatement(variable)
|
val newStatement = flow?.getTypeStatement(variable)
|
||||||
if (newStatement != from?.getTypeStatement(variable)) {
|
if (newStatement != previous?.getTypeStatement(variable)) {
|
||||||
receiverUpdated(it.boundSymbol, newStatement)
|
receiverUpdated(it.boundSymbol, newStatement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentSmartCastPosition = flow
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSameValueIn(other: PersistentFlow, fir: FirElement, original: MutableFlow): Boolean {
|
private fun isSameValueIn(other: PersistentFlow, fir: FirElement, original: MutableFlow): Boolean {
|
||||||
@@ -1528,7 +1521,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
|
|
||||||
private fun MutableFlow.addTypeStatement(info: TypeStatement) {
|
private fun MutableFlow.addTypeStatement(info: TypeStatement) {
|
||||||
val newStatement = logicSystem.addTypeStatement(this, info) ?: return
|
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)
|
receiverUpdated(newStatement.variable.identifier.symbol, newStatement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -177,7 +177,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
|
|||||||
when (result) {
|
when (result) {
|
||||||
is FirQualifiedAccessExpression -> {
|
is FirQualifiedAccessExpression -> {
|
||||||
dataFlowAnalyzer.exitQualifiedAccessExpression(result)
|
dataFlowAnalyzer.exitQualifiedAccessExpression(result)
|
||||||
result = components.transformQualifiedAccessUsingSmartcastInfo(result, ignoreCallArguments = false)
|
result = components.transformQualifiedAccessUsingSmartcastInfo(result)
|
||||||
if (result is FirSmartCastExpression) {
|
if (result is FirSmartCastExpression) {
|
||||||
dataFlowAnalyzer.exitSmartCastExpression(result)
|
dataFlowAnalyzer.exitSmartCastExpression(result)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user