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 57ee5aaa6b0..97e8a4c74b3 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 @@ -583,13 +583,16 @@ abstract class FirDataFlowAnalyzer( // ----------------------------------- Resolvable call ----------------------------------- - fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { - enterSafeCall(qualifiedAccessExpression) + // Intentionally left empty for potential future needs (call sites are preserved) + fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {} + + fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { + graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() + processConditionalContract(qualifiedAccessExpression) } - private fun enterSafeCall(qualifiedAccess: FirQualifiedAccess) { - if (!qualifiedAccess.safe) return - val node = graphBuilder.enterSafeCall(qualifiedAccess).mergeIncomingFlow() + fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) { + val node = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow() val previousNode = node.firstPreviousNode val shouldFork: Boolean var flow = if (previousNode is ExitSafeCallNode) { @@ -599,7 +602,8 @@ abstract class FirDataFlowAnalyzer( shouldFork = true node.flow } - qualifiedAccess.explicitReceiver?.let { receiver -> + + safeCall.receiver.let { receiver -> val type = receiver.coneType ?.takeIf { it.isMarkedNullable } ?.withNullability(ConeNullability.NOT_NULL) @@ -623,10 +627,20 @@ abstract class FirDataFlowAnalyzer( node.flow = flow } - fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { - graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() - processConditionalContract(qualifiedAccessExpression) - exitSafeCall(qualifiedAccessExpression) + fun exitSafeCall(safeCall: FirSafeCallExpression) { + val node = graphBuilder.exitSafeCall(safeCall).mergeIncomingFlow() + val previousFlow = node.previousFlow + + val variable = variableStorage.getOrCreateVariable(previousFlow, safeCall) + val receiverVariable = when (variable) { + // There is some bug with invokes. See KT-36014 + is RealVariable -> variable.explicitReceiverVariable ?: return + is SyntheticVariable -> variableStorage.getOrCreateVariable(previousFlow, safeCall.receiver) + } + logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable notEq null)) + if (receiverVariable.isReal()) { + logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable typeEq any)) + } } fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) { @@ -645,7 +659,6 @@ abstract class FirDataFlowAnalyzer( exitBooleanNot(functionCall, functionCallNode) } processConditionalContract(functionCall) - exitSafeCall(functionCall) } fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) { @@ -661,22 +674,6 @@ abstract class FirDataFlowAnalyzer( } } - private fun exitSafeCall(qualifiedAccess: FirQualifiedAccess) { - if (!qualifiedAccess.safe) return - val node = graphBuilder.exitSafeCall(qualifiedAccess).mergeIncomingFlow() - val previousFlow = node.previousFlow - val variable = variableStorage.getOrCreateVariable(previousFlow, qualifiedAccess) - val receiverVariable = when (variable) { - // There is some bug with invokes. See KT-36014 - is RealVariable -> variable.explicitReceiverVariable ?: return - is SyntheticVariable -> variableStorage.getOrCreateVariable(previousFlow, qualifiedAccess.explicitReceiver!!) - } - logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable notEq null)) - if (receiverVariable.isReal()) { - logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable typeEq any)) - } - } - private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) { val owner: FirContractDescriptionOwner? = when (qualifiedAccess) { is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorage.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorage.kt index b663630cbcb..79062e922e3 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorage.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorage.kt @@ -21,9 +21,7 @@ import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.ConeClassLikeType -import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.coneTypeSafe -import org.jetbrains.kotlin.fir.types.coneTypeUnsafe import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -47,6 +45,8 @@ class VariableStorage(private val session: FirSession) { private fun FirElement.unwrapElement(): FirElement = when (this) { is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject }?.unwrapElement() ?: this is FirExpressionWithSmartcast -> originalExpression.unwrapElement() + is FirSafeCallExpression -> regularQualifiedAccess.unwrapElement() + is FirCheckedSafeCallSubject -> originalReceiverReference.value.unwrapElement() else -> this } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt index 858f50d00c8..b5a632b38c1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -570,12 +570,12 @@ class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAc } } -class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id) { +class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode(owner, level, id) { override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitEnterSafeCallNode(this, data) } } -class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id) { +class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode(owner, level, id) { val lastPreviousNode: CFGNode<*> get() = previousNodes.last() val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index 7249da907ba..7e38c693d81 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -860,18 +860,18 @@ class ControlFlowGraphBuilder { // ----------------------------------- Safe calls ----------------------------------- - fun enterSafeCall(qualifiedAccess: FirQualifiedAccess): EnterSafeCallNode { + fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode { val lastNode = lastNodes.pop() - val enterNode = createEnterSafeCallNode(qualifiedAccess) + val enterNode = createEnterSafeCallNode(safeCall) lastNodes.push(enterNode) - val exitNode = createExitSafeCallNode(qualifiedAccess) + val exitNode = createExitSafeCallNode(safeCall) exitSafeCallNodes.push(exitNode) addEdge(lastNode, enterNode) addEdge(lastNode, exitNode) return enterNode } - fun exitSafeCall(qualifiedAccess: FirQualifiedAccess): ExitSafeCallNode { + fun exitSafeCall(safeCall: FirSafeCallExpression): ExitSafeCallNode { return exitSafeCallNodes.pop().also { addNewSimpleNode(it) it.updateDeadStatus() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt index e43cb938afd..eacf2745339 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt @@ -183,10 +183,10 @@ fun ControlFlowGraphBuilder.createBinaryAndEnterRightOperandNode(fir: FirBinaryL fun ControlFlowGraphBuilder.createBinaryOrEnterRightOperandNode(fir: FirBinaryLogicExpression): BinaryOrEnterRightOperandNode = BinaryOrEnterRightOperandNode(graph, fir, levelCounter, createId()) -fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirQualifiedAccess): ExitSafeCallNode = +fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirSafeCallExpression): ExitSafeCallNode = ExitSafeCallNode(graph, fir, levelCounter, createId()) -fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirQualifiedAccess): EnterSafeCallNode = +fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirSafeCallExpression): EnterSafeCallNode = EnterSafeCallNode(graph, fir, levelCounter, createId()) fun ControlFlowGraphBuilder.createPostponedLambdaExitNode(fir: FirAnonymousFunction): PostponedLambdaExitNode = @@ -212,4 +212,4 @@ fun ControlFlowGraphBuilder.createClassExitNode(fir: FirClass<*>): ClassExitNode } fun ControlFlowGraphBuilder.createLocalClassExitNode(fir: FirRegularClass): LocalClassExitNode = - LocalClassExitNode(graph, fir, levelCounter, createId()) \ No newline at end of file + LocalClassExitNode(graph, fir, levelCounter, createId()) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt index a3c6006f497..8019384c44d 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt @@ -6,25 +6,18 @@ package org.jetbrains.kotlin.fir.resolve.dfa import kotlinx.collections.immutable.PersistentMap -import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.fir.FirElement -import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference -import org.jetbrains.kotlin.fir.declarations.* -import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate -import org.jetbrains.kotlin.fir.resolve.directExpansionType -import org.jetbrains.kotlin.fir.resolve.fullyExpandedType -import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol -import org.jetbrains.kotlin.fir.symbols.impl.* -import org.jetbrains.kotlin.fir.types.ConeClassLikeType -import org.jetbrains.kotlin.fir.types.ConeFlexibleType +import org.jetbrains.kotlin.fir.symbols.impl.FirAccessorSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -130,6 +123,7 @@ internal val FirElement.symbol: AbstractFirBasedSymbol<*>? is FirResolvable -> symbol is FirSymbolOwner<*> -> symbol is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol + is FirSafeCallExpression -> regularQualifiedAccess.symbol else -> null }?.takeIf { this is FirThisReceiverExpression || (it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol) }