Repository: intellij-rust/intellij-rust
Issue: Infinite loops in async context causes variables to be marked as unused

## Environment

* **IntelliJ Rust plugin version:** 0.3.130.3337-202
* **Rust toolchain version:** rustc 1.48.0-nightly (f68e08933 2020-09-19)
* **IDE name and version:** IntelliJ Ultimate 2020.2.2 Build #IU-202.7319.50
* **Operating system:** Arch Linux

## Problem description
Variables before an infinite loop in an `async` block are marked as unused, because the IDE thinks that the code after it is unreachable. That would be the case if `await`ed immediately, but when e.g. spawning it on another thread, that is not the case.

## Steps to reproduce
```rust
fn foo() {
    let a = 5;
    let _ = async { loop {} };
    dbg!(a);
}
```
Observe how `a` is grayed out as the IDE thinks that `dbg!(a)` can't be reached.

Changed files:
src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt, src/test/kotlin/org/rust/ide/inspections/lints/RsLivenessInspectionTest.kt, src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt, src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt

diff --git a/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt b/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
index 3d57c2b5b..49f86f518 100644
--- a/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
+++ b/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
@@ -339,18 +339,24 @@ class CFGBuilder(
 
     override fun visitBlockExpr(blockExpr: RsBlockExpr) {
         val labelDeclaration = blockExpr.labelDecl
+        val isAsync = blockExpr.isAsync
+        val exprExit = addAstNode(blockExpr)
+
         if (labelDeclaration != null) {
-            val exprExit = addAstNode(blockExpr)
             withBlockScope(BlockScope(blockExpr, exprExit)) {
                 val stmtsExit = blockExpr.block.stmtList.fold(pred) { pred, stmt -> process(stmt, pred) }
                 val blockExprExit = process(blockExpr.block.expr, stmtsExit)
                 addContainedEdge(blockExprExit, exprExit)
             }
-            finishWith(exprExit)
         } else {
             val blockExit = process(blockExpr.block, pred)
-            finishWithAstNode(blockExpr, blockExit)
+            addContainedEdge(blockExit, exprExit)
+        }
+
+        if (isAsync) {
+            addContainedEdge(pred, exprExit)
         }
+        finishWith(exprExit)
     }
 
     override fun visitIfExpr(ifExpr: RsIfExpr) {
diff --git a/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt b/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
index a04f27503..b26006947 100644
--- a/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
+++ b/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
@@ -5,8 +5,10 @@
 
 package org.rust.ide.inspections.borrowck
 
+import org.rust.MockEdition
 import org.rust.ProjectDescriptor
 import org.rust.WithStdlibRustProjectDescriptor
+import org.rust.cargo.project.workspace.CargoWorkspace
 import org.rust.ide.inspections.RsBorrowCheckerInspection
 import org.rust.ide.inspections.RsInspectionsTestBase
 
@@ -720,4 +722,15 @@ class RsBorrowCheckerMovesTest : RsInspectionsTestBase(RsBorrowCheckerInspection
             s.x;
         }
     """, checkWarn = false)
+
+    @MockEdition(CargoWorkspace.Edition.EDITION_2018)
+    fun `test move in async block`() = checkByText("""
+        struct S;
+        
+        fn foo() {
+            let s = S;
+            async { s; };
+            <error descr="Use of moved value">s</error>;
+        }
+    """, checkWarn = false)
 }
diff --git a/src/test/kotlin/org/rust/ide/inspections/lints/RsLivenessInspectionTest.kt b/src/test/kotlin/org/rust/ide/inspections/lints/RsLivenessInspectionTest.kt
index 3f9dce4c0..a5353179f 100644
--- a/src/test/kotlin/org/rust/ide/inspections/lints/RsLivenessInspectionTest.kt
+++ b/src/test/kotlin/org/rust/ide/inspections/lints/RsLivenessInspectionTest.kt
@@ -778,4 +778,12 @@ class RsLivenessInspectionTest : RsInspectionsTestBase(RsLivenessInspection::cla
             s.foo(2);
         }
     """)
+
+    fun `test use after async block with infinite loop`() = checkByText("""
+        fn foo() {
+            let a = 1;
+            let _ = async { loop {} };
+            a;
+        }
+    """)
 }
diff --git a/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt b/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
index f438e6324..f8541d1ee 100644
--- a/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
@@ -7,7 +7,9 @@ package org.rust.lang.core.cfg
 
 import junit.framework.ComparisonFailure
 import org.intellij.lang.annotations.Language
+import org.rust.MockEdition
 import org.rust.RsTestBase
+import org.rust.cargo.project.workspace.CargoWorkspace
 import org.rust.lang.core.psi.RsFunction
 import org.rust.lang.core.psi.ext.block
 import org.rust.lang.core.psi.ext.descendantsOfType
@@ -974,6 +976,27 @@ class RsControlFlowGraphTest : RsTestBase() {
         Termination
     """)
 
+    @MockEdition(CargoWorkspace.Edition.EDITION_2018)
+    fun `test async block with infinite loop`() = testCFG("""
+        fn foo() {
+            1;
+            async { loop {} };
+            2;
+        }
+    """, """
+        Entry
+        1
+        1;
+        Dummy
+        BLOCK
+        Termination
+        BLOCK
+        BLOCK;
+        2
+        2;
+        BLOCK
+        Exit
+    """)
 
     private fun testCFG(@Language("Rust") code: String, expectedIndented: String) {
         InlineFile(code)