From 8a68eac5f1bbc5c454f6fe0d3512faf924ea6771 Mon Sep 17 00:00:00 2001 From: pyos Date: Tue, 22 Nov 2022 11:08:05 +0100 Subject: [PATCH] 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 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. --- .../delegates/delegateWithAnonymousObject.dot | 20 ++++++++-------- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 8 +++++-- .../dfa/cfg/ControlFlowGraphBuilder.kt | 23 +++++++++++++++---- .../dfa/cfg/ControlFlowGraphNodeBuilder.kt | 3 +++ .../FirDeclarationsResolveTransformer.kt | 8 +++---- .../kotlin/fir/resolve/dfa/cfg/CFGNode.kt | 8 +++++++ .../fir/resolve/dfa/cfg/CFGNodeRenderer.kt | 1 + .../dfa/cfg/ControlFlowGraphVisitor.kt | 4 ++++ .../dfa/cfg/ControlFlowGraphVisitorVoid.kt | 8 +++++++ 9 files changed, 64 insertions(+), 19 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateWithAnonymousObject.dot b/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateWithAnonymousObject.dot index 9f8d6869674..4bd82969b04 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateWithAnonymousObject.dot +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateWithAnonymousObject.dot @@ -225,7 +225,8 @@ digraph delegateWithAnonymousObject_kt { 74 [label="Function call: this@R|/IssuesListUserProfile|.R|/delegate|(...)" style="filled" fillcolor=yellow]; 75 [label="Access variable this@R|/IssuesListUserProfile|"]; 76 [label="Function call: this@R|/IssuesListUserProfile|.R|/delegate|(...).#(...)" 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]; } 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 32619da5663..783845a2657 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 @@ -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 ----------------------------------- 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 4ff7f3a505a..d5dbea92fb7 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 @@ -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.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 { 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 7f184bb14de..59391d2cbea 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 @@ -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()) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt index 5ae2fd61687..ad46c75b971 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt @@ -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) } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt index 4a20db3b7e6..a28a0003840 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt @@ -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(owner, level, id), UnionNodeMarker { + override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { + return visitor.visitDelegateExpressionExitNode(this, data) + } +} + // ----------------------------------- Field ----------------------------------- @OptIn(CfgInternals::class) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt index a1c7b8bb482..43780d87685 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNodeRenderer.kt @@ -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" diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt index cb849c9b16f..054224b2708 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitor.kt @@ -104,6 +104,10 @@ abstract class ControlFlowGraphVisitor { 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 { diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt index f1dfeac56c5..2247ef82b5b 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphVisitorVoid.kt @@ -50,6 +50,10 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor