FIR: Support safe-calls new format in DFA
^KT-38444 In Progress
This commit is contained in:
+24
-27
@@ -583,13 +583,16 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
|
||||
// ----------------------------------- 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<FLOW : Flow>(
|
||||
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<FLOW : Flow>(
|
||||
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<FLOW : Flow>(
|
||||
exitBooleanNot(functionCall, functionCallNode)
|
||||
}
|
||||
processConditionalContract(functionCall)
|
||||
exitSafeCall(functionCall)
|
||||
}
|
||||
|
||||
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
|
||||
@@ -661,22 +674,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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<FirQualifiedAccess>(owner, level, id) {
|
||||
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitEnterSafeCallNode(this, data)
|
||||
}
|
||||
}
|
||||
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id) {
|
||||
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
|
||||
val lastPreviousNode: CFGNode<*> get() = previousNodes.last()
|
||||
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
|
||||
|
||||
|
||||
+4
-4
@@ -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()
|
||||
|
||||
+3
-3
@@ -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())
|
||||
LocalClassExitNode(graph, fir, levelCounter, createId())
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user