FIR: fork flow on function entry, restore receivers on exit

^KT-52680 Fixed
This commit is contained in:
pyos
2022-06-09 11:34:59 +02:00
committed by Dmitriy Novozhilov
parent 3ba11711d2
commit 9968fa252a
11 changed files with 116 additions and 23 deletions
@@ -28558,6 +28558,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt");
}
@Test
@TestMetadata("implicitReceiverAcrossFunctions.kt")
public void testImplicitReceiverAcrossFunctions() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiverAcrossFunctions.kt");
}
@Test
@TestMetadata("implicitThisOnRefInLambdaInSmartcast.kt")
public void testImplicitThisOnRefInLambdaInSmartcast() throws Exception {
@@ -28558,6 +28558,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt");
}
@Test
@TestMetadata("implicitReceiverAcrossFunctions.kt")
public void testImplicitReceiverAcrossFunctions() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiverAcrossFunctions.kt");
}
@Test
@TestMetadata("implicitThisOnRefInLambdaInSmartcast.kt")
public void testImplicitThisOnRefInLambdaInSmartcast() throws Exception {
@@ -28558,6 +28558,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt");
}
@Test
@TestMetadata("implicitReceiverAcrossFunctions.kt")
public void testImplicitReceiverAcrossFunctions() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiverAcrossFunctions.kt");
}
@Test
@TestMetadata("implicitThisOnRefInLambdaInSmartcast.kt")
public void testImplicitThisOnRefInLambdaInSmartcast() throws Exception {
@@ -269,6 +269,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (graphBuilder.isTopLevel()) {
context.reset()
}
logicSystem.updateAllReceivers(graph.enterNode.computeIncomingFlow().first)
return FirControlFlowGraphReferenceImpl(graph, DataFlowInfo(variableStorage, flowOnNodes))
}
@@ -282,7 +283,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val (postponedLambdaEnterNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
// TODO: questionable
postponedLambdaEnterNode?.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow(shouldForkFlow = true)
logicSystem.updateAllReceivers(functionEnterNode.flow)
}
@@ -294,6 +295,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// TODO: questionable
postponedLambdaExitNode?.mergeIncomingFlow()
functionExitNode.mergeIncomingFlow()
logicSystem.updateAllReceivers(graph.enterNode.computeIncomingFlow().first)
return FirControlFlowGraphReferenceImpl(graph)
}
@@ -1512,16 +1514,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
private val CFGNode<*>.origin: CFGNode<*> get() = if (this is StubNode) firstPreviousNode else this
private fun <T : CFGNode<*>> T.mergeIncomingFlow(
// This flag should be set true if we're changing flow branches from one to another (e.g. in when, try->catch)
updateReceivers: Boolean = false,
shouldForkFlow: Boolean = false
): T = this.also { node ->
private fun <T : CFGNode<*>> T.computeIncomingFlow(): Pair<FLOW, Int> {
val previousFlows = mutableListOf<FLOW>()
var deadForwardCount = 0
for (previousNode in previousNodes) {
val incomingEdgeKind = node.incomingEdges.getValue(previousNode).kind
if (node.isDead) {
val incomingEdgeKind = incomingEdges.getValue(previousNode).kind
if (isDead) {
if (!incomingEdgeKind.isBack) {
previousFlows += previousNode.flow
}
@@ -1532,9 +1530,16 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
deadForwardCount++
}
}
var flow = logicSystem.joinFlow(previousFlows)
// deadForwardCount should be added due to cases like merge after 'if (...) return else ...'
if (updateReceivers || previousFlows.size + deadForwardCount > 1) {
return logicSystem.joinFlow(previousFlows) to (previousFlows.size + deadForwardCount)
}
private fun <T : CFGNode<*>> T.mergeIncomingFlow(
// This flag should be set true if we're changing flow branches from one to another (e.g. in when, try->catch)
updateReceivers: Boolean = false,
shouldForkFlow: Boolean = false
): T = this.also { node ->
var (flow, incomingEdges) = computeIncomingFlow()
if (updateReceivers || incomingEdges > 1) {
logicSystem.updateAllReceivers(flow)
}
if (shouldForkFlow) {
@@ -0,0 +1,24 @@
interface I {
val prop: Int
}
open class A {
fun f1() {
this as I
prop
}
fun f2() {
<!UNRESOLVED_REFERENCE!>prop<!>
}
}
open class B {
fun f() {
{
this as I
prop
}
<!UNRESOLVED_REFERENCE!>prop<!>
}
}
@@ -0,0 +1,24 @@
interface I {
val prop: Int
}
open class A {
fun f1() {
this as I
<!DEBUG_INFO_IMPLICIT_RECEIVER_SMARTCAST!>prop<!>
}
fun f2() {
<!UNRESOLVED_REFERENCE!>prop<!>
}
}
open class B {
fun f() {
{
this as I
<!DEBUG_INFO_IMPLICIT_RECEIVER_SMARTCAST!>prop<!>
}
<!UNRESOLVED_REFERENCE!>prop<!>
}
}
@@ -0,0 +1,25 @@
package
public open class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun f1(): kotlin.Unit
public final fun f2(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class B {
public constructor B()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun f(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface I {
public abstract val prop: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -1,10 +0,0 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// Exotic variant with unused literal
do { ->
p!!.length
} while (!x())
// Literal is not called so p.length is unsafe
return p.length
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
@@ -28648,6 +28648,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt");
}
@Test
@TestMetadata("implicitReceiverAcrossFunctions.kt")
public void testImplicitReceiverAcrossFunctions() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/implicitReceiverAcrossFunctions.kt");
}
@Test
@TestMetadata("implicitThisOnRefInLambdaInSmartcast.kt")
public void testImplicitThisOnRefInLambdaInSmartcast() throws Exception {
@@ -19,8 +19,8 @@ fun case_2(x: Int?) = 10
fun case_2() {
var x: Int? = 10
var y = { x = null }
if (<!SENSELESS_COMPARISON!>x != null<!>) {
val z = case_2(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Nothing")!>x<!>)
if (x != null) {
val z = case_2(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String")!>z<!>
}
}