FIR DFA: use local function declaration nodes for non-argument lambdas

This commit is contained in:
pyos
2022-11-22 20:01:20 +01:00
committed by teamcity
parent 8a68eac5f1
commit b2fe5831ed
6 changed files with 24 additions and 40 deletions
@@ -113,7 +113,7 @@ digraph lambdas_kt {
subgraph cluster_13 {
color=blue
39 [label="Enter block"];
40 [label="Postponed enter to lambda"];
40 [label="Local function declaration test_2"];
41 [label="Exit anonymous function expression"];
42 [label="Variable declaration: lval lambda: R|() -> kotlin/Int|"];
43 [label="Exit block"];
@@ -43,10 +43,6 @@ class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlow
lambdaOrLocalFunctionStack.add(node.fir)
}
override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) {
lambdaOrLocalFunctionStack.remove(node.fir.anonymousFunction)
}
override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) {
lambdaOrLocalFunctionStack.add(node.fir)
}
@@ -151,12 +151,12 @@ abstract class FirDataFlowAnalyzer(
return variable.stability to types.toMutableList()
}
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> {
return graphBuilder.returnExpressionsOfAnonymousFunction(function)
}
fun returnExpressionsOfAnonymousFunctionOrNull(function: FirAnonymousFunction): Collection<FirStatement>? =
graphBuilder.returnExpressionsOfAnonymousFunction(function)
fun isThereControlFlowInfoForAnonymousFunction(function: FirAnonymousFunction): Boolean =
graphBuilder.isThereControlFlowInfoForAnonymousFunction(function)
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> =
returnExpressionsOfAnonymousFunctionOrNull(function)
?: error("anonymous function ${function.render()} not analyzed")
fun dropSubgraphFromCall(call: FirFunctionCall) {
graphBuilder.dropSubgraphFromCall(call)
@@ -220,8 +220,8 @@ abstract class FirDataFlowAnalyzer(
finishPostponedAnonymousFunction()
enterLocalFunction(anonymousFunction)
}
val (postponedLambdaEnterNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
postponedLambdaEnterNode?.mergeIncomingFlow()
val (functionDeclarationNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
functionDeclarationNode?.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow {
if (anonymousFunction.invocationKind?.canBeRevisited() != false) {
enterCapturingStatement(it, anonymousFunction)
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildAnonymousFunctionExpression
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.dfa.*
@@ -57,7 +56,6 @@ class ControlFlowGraphBuilder {
private val exitTargetsForReturn: SymbolBasedNodeStorage<FirFunction, FunctionExitNode> = SymbolBasedNodeStorage()
private val exitTargetsForTry: Stack<CFGNode<*>> = stackOf()
private val exitsOfAnonymousFunctions: MutableMap<FirFunctionSymbol<*>, FunctionExitNode> = mutableMapOf()
private val enterToLocalClassesMembers: MutableMap<FirBasedSymbol<*>, CFGNode<*>?> = mutableMapOf()
//return jumps via finally blocks, target -> jumps
@@ -98,12 +96,10 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Public API -----------------------------------
fun isThereControlFlowInfoForAnonymousFunction(function: FirAnonymousFunction): Boolean =
function.controlFlowGraphReference?.controlFlowGraph != null ||
exitsOfAnonymousFunctions.containsKey(function.symbol)
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement>? {
val exitNode = function.controlFlowGraphReference?.controlFlowGraph?.exitNode
?: return null
// This function might throw exception if !isThereControlFlowInfoForAnonymousFunction(function)
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> {
fun FirElement.extractArgument(): FirElement = when {
this is FirReturnExpression && target.labeledElement.symbol == function.symbol -> result.extractArgument()
else -> this
@@ -116,10 +112,7 @@ class ControlFlowGraphBuilder {
else -> fir.extractArgument()
}
val exitNode = function.controlFlowGraphReference?.controlFlowGraph?.exitNode
?: exitsOfAnonymousFunctions.getValue(function.symbol)
val nonDirect = nonDirectJumps[exitNode]
return (nonDirect + exitNode.previousNodes).mapNotNullTo(mutableSetOf()) {
return (nonDirectJumps[exitNode] + exitNode.previousNodes).mapNotNullTo(mutableSetOf()) {
it.extractArgument() as FirStatement?
}
}
@@ -257,29 +250,27 @@ class ControlFlowGraphBuilder {
return enterNode to exitNode
}
fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction): Pair<PostponedLambdaEnterNode?, FunctionEnterNode> {
fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction): Pair<LocalFunctionDeclarationNode?, FunctionEnterNode> {
val symbol = anonymousFunction.symbol
val preparedEnterNode = postponedAnonymousFunctionNodes[symbol]?.first
val outerEnterNode = preparedEnterNode ?: createPostponedLambdaEnterNode(anonymousFunction).also { addNewSimpleNode(it) }
val flowSourceNode = postponedAnonymousFunctionNodes[symbol]?.first
?: createLocalFunctionDeclarationNode(anonymousFunction).also { addNewSimpleNode(it) }
pushGraph(ControlFlowGraph(anonymousFunction, "<anonymous>", ControlFlowGraph.Kind.AnonymousFunction), Mode.Function)
val enterNode = createFunctionEnterNode(anonymousFunction)
val exitNode = createFunctionExitNode(anonymousFunction)
exitsOfAnonymousFunctions[symbol] = exitNode
exitTargetsForReturn.push(exitNode)
if (!anonymousFunction.invocationKind.isInPlace) {
exitTargetsForTry.push(exitNode)
}
addEdge(outerEnterNode, enterNode)
addEdge(flowSourceNode, enterNode)
lastNodes.push(enterNode)
return outerEnterNode.takeIf { preparedEnterNode == null } to enterNode
return (flowSourceNode as? LocalFunctionDeclarationNode) to enterNode
}
fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple<FunctionExitNode, PostponedLambdaExitNode?, ControlFlowGraph> {
val symbol = anonymousFunction.symbol
val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also {
require(it == exitTargetsForReturn.pop())
val exitNode = exitTargetsForReturn.pop().also {
if (!anonymousFunction.invocationKind.isInPlace) {
require(it == exitTargetsForTry.pop())
}
@@ -295,7 +286,7 @@ class ControlFlowGraphBuilder {
}
val (postponedEnterNode, postponedExitNode) = postponedAnonymousFunctionNodes.remove(symbol)
?: return Triple(exitNode, null, graph).also { (lastNode as PostponedLambdaEnterNode).owner.addSubGraph(graph) }
?: return Triple(exitNode, null, graph).also { currentGraph.addSubGraph(graph) }
val invocationKind = anonymousFunction.invocationKind
var changedExitDataFlow = false
@@ -327,6 +318,8 @@ class ControlFlowGraphBuilder {
}
}
postponedEnterNode.addSubGraph(graph)
// May not be the current graph: `nearestCompletedCall(run { run { generic() } }, 1)`
postponedEnterNode.owner.addSubGraph(graph)
return Triple(exitNode, postponedExitNode.takeIf { changedExitDataFlow }, graph)
}
@@ -1324,7 +1317,6 @@ class ControlFlowGraphBuilder {
// -------------------------------------------------------------------------------------------------------------------------
fun reset() {
exitsOfAnonymousFunctions.clear()
dataFlowSourcesForNextCompletedCall.reset()
lastNodes.reset()
}
@@ -572,10 +572,8 @@ class FirCallCompletionResultsWriterTransformer(
// Control flow info is necessary prerequisite because we collect return expressions in that function
//
// Example: second lambda in the call like list.filter({}, {})
if (!dataFlowAnalyzer.isThereControlFlowInfoForAnonymousFunction(anonymousFunction)) {
// But, don't leave implicit type refs behind
return transformImplicitTypeRefInAnonymousFunction(anonymousFunction)
}
val returnExpressionsOfAnonymousFunction = dataFlowAnalyzer.returnExpressionsOfAnonymousFunctionOrNull(anonymousFunction)
?: return transformImplicitTypeRefInAnonymousFunction(anonymousFunction)
val expectedType = data?.getExpectedType(anonymousFunction)?.let { expectedArgumentType ->
// From the argument mapping, the expected type of this anonymous function would be:
@@ -645,8 +643,6 @@ class FirCallCompletionResultsWriterTransformer(
val result = transformElement(anonymousFunction, null)
val returnExpressionsOfAnonymousFunction: Collection<FirStatement> =
dataFlowAnalyzer.returnExpressionsOfAnonymousFunction(anonymousFunction)
for (expression in returnExpressionsOfAnonymousFunction) {
expression.transform<FirElement, ExpectedArgumentType?>(this, finalType?.toExpectedType())
}
@@ -240,7 +240,7 @@ class ExitDefaultArgumentsNode(owner: ControlFlowGraph, override val fir: FirVal
// ----------------------------------- Anonymous function -----------------------------------
class PostponedLambdaEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousFunction, level: Int, id: Int) : CFGNodeWithCfgOwner<FirAnonymousFunction>(owner, level, id) {
class PostponedLambdaEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousFunction, level: Int, id: Int) : CFGNodeWithSubgraphs<FirAnonymousFunction>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPostponedLambdaEnterNode(this, data)
}