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:
pyos
2022-11-22 11:08:05 +01:00
committed by teamcity
parent a9be27e330
commit 8a68eac5f1
9 changed files with 64 additions and 19 deletions
@@ -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];
}
@@ -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 -----------------------------------
@@ -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 {
@@ -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())
@@ -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"
@@ -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 {
@@ -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?) {