diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/resolver/resolverUtils.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/resolver/resolverUtils.kt index 7913127c93c..8fa9da2edd0 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/resolver/resolverUtils.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/resolver/resolverUtils.kt @@ -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>? = + override fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair>? = null } } \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt index 3c953b5cb94..bab83b22503 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt @@ -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 diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirInvokeResolveTowerExtension.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirInvokeResolveTowerExtension.kt index 39b01973fc9..fa398381da1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirInvokeResolveTowerExtension.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirInvokeResolveTowerExtension.kt @@ -344,7 +344,7 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable( }.build().let { callCompleter.completeCall(it, ResolutionMode.ReceiverResolution) }.let { - transformQualifiedAccessUsingSmartcastInfo(it, ignoreCallArguments = true) + transformQualifiedAccessUsingSmartcastInfo(it) } } 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 f78f0783daa..d564b4d7cb9 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 @@ -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>? { - // 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>? { + 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) } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt index 11a1579c69e..483361541eb 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt @@ -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) }