FIR DFA: add a union node for property delegates
The delegate is resolved in context-dependent mode and thus can be an incomplete call; if there is no `provideDelegate` method to complete it, the result is effectively `val x$delegate = y.id()` where `id` is `fun <T> id(x: T) = x`, except we don't get a real node for `id` so the DFA edges from lambdas in `y` go who knows where.
This commit is contained in:
Vendored
+11
-9
@@ -225,7 +225,8 @@ digraph delegateWithAnonymousObject_kt {
|
||||
74 [label="Function call: this@R|/IssuesListUserProfile|.R|/delegate|<R|IssuesListUserProfile|, R|IssuesListUserProfile|, R|IssueListView|>(...)" style="filled" fillcolor=yellow];
|
||||
75 [label="Access variable this@R|/IssuesListUserProfile|"];
|
||||
76 [label="Function call: this@R|/IssuesListUserProfile|.R|/delegate|<R|IssuesListUserProfile|, R|IssuesListUserProfile|, R|IssueListView|>(...).<Unresolved name: provideDelegate>#(...)" style="filled" fillcolor=yellow];
|
||||
77 [label="Exit property" style="filled" fillcolor=red];
|
||||
77 [label="Exit property delegate" style="filled" fillcolor=yellow];
|
||||
78 [label="Exit property" style="filled" fillcolor=red];
|
||||
}
|
||||
71 -> {72};
|
||||
72 -> {73};
|
||||
@@ -234,17 +235,18 @@ digraph delegateWithAnonymousObject_kt {
|
||||
74 -> {75};
|
||||
75 -> {76};
|
||||
76 -> {77};
|
||||
77 -> {80} [color=green];
|
||||
77 -> {78};
|
||||
78 -> {81} [color=green];
|
||||
|
||||
subgraph cluster_22 {
|
||||
color=red
|
||||
78 [label="Enter class IssuesListUserProfile" style="filled" fillcolor=red];
|
||||
79 [label="Part of class initialization"];
|
||||
80 [label="Exit class IssuesListUserProfile" style="filled" fillcolor=red];
|
||||
79 [label="Enter class IssuesListUserProfile" style="filled" fillcolor=red];
|
||||
80 [label="Part of class initialization"];
|
||||
81 [label="Exit class IssuesListUserProfile" style="filled" fillcolor=red];
|
||||
}
|
||||
78 -> {79} [color=green];
|
||||
79 -> {80} [style=dotted];
|
||||
79 -> {71} [color=green];
|
||||
79 -> {71} [style=dashed];
|
||||
79 -> {80} [color=green];
|
||||
80 -> {81} [style=dotted];
|
||||
80 -> {71} [color=green];
|
||||
80 -> {71} [style=dashed];
|
||||
|
||||
}
|
||||
|
||||
+6
-2
@@ -343,9 +343,13 @@ abstract class FirDataFlowAnalyzer(
|
||||
|
||||
// ----------------------------------- Delegate -----------------------------------
|
||||
|
||||
fun enterDelegateExpression() {}
|
||||
fun enterDelegateExpression() {
|
||||
graphBuilder.enterDelegateExpression()
|
||||
}
|
||||
|
||||
fun exitDelegateExpression() {}
|
||||
fun exitDelegateExpression(fir: FirExpression) {
|
||||
graphBuilder.exitDelegateExpression(fir).mergeIncomingFlow()
|
||||
}
|
||||
|
||||
// ----------------------------------- Block -----------------------------------
|
||||
|
||||
|
||||
+19
-4
@@ -306,13 +306,11 @@ class ControlFlowGraphBuilder {
|
||||
CFGNode.addJustKindEdge(postponedEnterNode, postponedExitNode, EdgeKind.CfgForward, propagateDeadness = true)
|
||||
}
|
||||
if (invocationKind?.canBeVisited() == true) {
|
||||
// TODO: existence of an `invocationKind` should imply that this is an argument to a call, and yet
|
||||
// replacing this with `top()` breaks things. Hmm.
|
||||
val currentCallsPostponedLambdas = dataFlowSourcesForNextCompletedCall.topOrNull()
|
||||
val currentCallsPostponedLambdas = dataFlowSourcesForNextCompletedCall.top()
|
||||
// Since the skipping edge goes to `postponedExitNode` rather than `exitNode`, if
|
||||
// we try to merge data flow for not-definitely-called lambdas from `exitNode` into the next call
|
||||
// we won't get a correct result. TODO: that seems hacky and points to the edges being wrong.
|
||||
if (currentCallsPostponedLambdas != null && invocationKind.isDefinitelyVisited()) {
|
||||
if (invocationKind.isDefinitelyVisited()) {
|
||||
// When a call has lambda arguments that the function says it will call in place, control goes through
|
||||
// all other arguments, then through the lambdas in parallel, then out of the function call. This parallel
|
||||
// (in terms of ordering, not multi-threaded or w/e) execution is represented with the call being a union node.
|
||||
@@ -647,6 +645,23 @@ class ControlFlowGraphBuilder {
|
||||
return exitNode to graph
|
||||
}
|
||||
|
||||
// ----------------------------------- Delegate -----------------------------------
|
||||
|
||||
fun enterDelegateExpression() {
|
||||
splitDataFlowForPostponedLambdas()
|
||||
}
|
||||
|
||||
fun exitDelegateExpression(fir: FirExpression): DelegateExpressionExitNode {
|
||||
return createDelegateExpressionExitNode(fir).also {
|
||||
// `val x by y` is resolved as either `val x$delegate = y.provideDelegate()` or `val x$delegate = y.id()`,
|
||||
// where `fun <T> T.id(): T`...except `id` doesn't exist, and what that means is that `y` is resolved in
|
||||
// context-dependent mode, and we don't necessarily get an enclosing completed call to unify data flow in.
|
||||
// This node serves as a substitute.
|
||||
unifyDataFlowFromPostponedLambdas(it, callCompleted = true)
|
||||
addNewSimpleNode(it)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------- Operator call -----------------------------------
|
||||
|
||||
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall): TypeOperatorCallNode {
|
||||
|
||||
+3
@@ -58,6 +58,9 @@ fun ControlFlowGraphBuilder.createPropertyInitializerExitNode(fir: FirProperty):
|
||||
fun ControlFlowGraphBuilder.createPropertyInitializerEnterNode(fir: FirProperty): PropertyInitializerEnterNode =
|
||||
PropertyInitializerEnterNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createDelegateExpressionExitNode(fir: FirExpression): DelegateExpressionExitNode =
|
||||
DelegateExpressionExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createFieldInitializerExitNode(fir: FirField): FieldInitializerExitNode =
|
||||
FieldInitializerExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
|
||||
+4
-4
@@ -160,7 +160,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
|
||||
}
|
||||
val delegate = property.delegate
|
||||
if (delegate != null) {
|
||||
transformPropertyAccessorsWithDelegate(property)
|
||||
transformPropertyAccessorsWithDelegate(property, delegate)
|
||||
if (property.delegateFieldSymbol != null) {
|
||||
replacePropertyReferenceTypeInDelegateAccessors(property)
|
||||
}
|
||||
@@ -268,7 +268,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
|
||||
(property.delegate as? FirFunctionCall)?.replacePropertyReferenceTypeInDelegateAccessors(property)
|
||||
}
|
||||
|
||||
private fun transformPropertyAccessorsWithDelegate(property: FirProperty) {
|
||||
private fun transformPropertyAccessorsWithDelegate(property: FirProperty, delegate: FirExpression) {
|
||||
context.forPropertyDelegateAccessors(property, resolutionContext, callCompleter) {
|
||||
dataFlowAnalyzer.enterDelegateExpression()
|
||||
// Resolve delegate expression, after that, delegate will contain either expr.provideDelegate or expr
|
||||
@@ -297,7 +297,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
|
||||
it.transformSingle(callCompletionResultsWriter, null)
|
||||
}
|
||||
|
||||
dataFlowAnalyzer.exitDelegateExpression()
|
||||
dataFlowAnalyzer.exitDelegateExpression(delegate)
|
||||
property
|
||||
}
|
||||
}
|
||||
@@ -380,7 +380,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
|
||||
val hadExplicitType = variable.returnTypeRef !is FirImplicitTypeRef
|
||||
|
||||
if (delegate != null) {
|
||||
transformPropertyAccessorsWithDelegate(variable)
|
||||
transformPropertyAccessorsWithDelegate(variable, delegate)
|
||||
if (variable.delegateFieldSymbol != null) {
|
||||
replacePropertyReferenceTypeInDelegateAccessors(variable)
|
||||
}
|
||||
|
||||
@@ -362,6 +362,14 @@ class PropertyInitializerExitNode(owner: ControlFlowGraph, override val fir: Fir
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DelegateExpressionExitNode(owner: ControlFlowGraph, override val fir: FirExpression, level: Int, id: Int)
|
||||
: CFGNode<FirExpression>(owner, level, id), UnionNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitDelegateExpressionExitNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------- Field -----------------------------------
|
||||
|
||||
@OptIn(CfgInternals::class)
|
||||
|
||||
@@ -85,6 +85,7 @@ fun CFGNode<*>.render(): String =
|
||||
is PartOfClassInitializationNode -> "Part of class initialization"
|
||||
is PropertyInitializerEnterNode -> "Enter property"
|
||||
is PropertyInitializerExitNode -> "Exit property"
|
||||
is DelegateExpressionExitNode -> "Exit property delegate"
|
||||
is FieldInitializerEnterNode -> "Enter field"
|
||||
is FieldInitializerExitNode -> "Exit field"
|
||||
is InitBlockEnterNode -> "Enter init block"
|
||||
|
||||
+4
@@ -104,6 +104,10 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitDelegateExpressionExitNode(node: DelegateExpressionExitNode, data: D): R {
|
||||
return visitUnionNode(node, data)
|
||||
}
|
||||
|
||||
// ----------------------------------- Field -----------------------------------
|
||||
|
||||
open fun visitFieldInitializerEnterNode(node: FieldInitializerEnterNode, data: D): R {
|
||||
|
||||
+8
@@ -50,6 +50,10 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitDelegateExpressionExitNode(node: DelegateExpressionExitNode) {
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
// ----------------------------------- Init -----------------------------------
|
||||
|
||||
open fun visitInitBlockEnterNode(node: InitBlockEnterNode) {
|
||||
@@ -339,6 +343,10 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitPropertyInitializerExitNode(node)
|
||||
}
|
||||
|
||||
final override fun visitDelegateExpressionExitNode(node: DelegateExpressionExitNode, data: Nothing?) {
|
||||
visitDelegateExpressionExitNode(node)
|
||||
}
|
||||
|
||||
// ----------------------------------- Init -----------------------------------
|
||||
|
||||
final override fun visitInitBlockEnterNode(node: InitBlockEnterNode, data: Nothing?) {
|
||||
|
||||
Reference in New Issue
Block a user