Repository: quora/pyanalyze (python)

Issue:
Coroutines + NoReturn doesn't work
Pyanalyze doesn't recognize that code after `await coroutine_returning_NoReturn()` is unreachable.

Pull request description:
handle NoReturn in async def
Fixes #414

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 218de84..9031549 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Handle `NoReturn` in `async def` functions (#427)
 - Support PEP 673 (`typing_extensions.Self`) (#423)
 - Updates for compatibility with recent changes in typeshed (#421):
   - Fix override compatibility check for unknown callables 
diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py
index 47d604a..6d99087 100644
--- a/pyanalyze/name_check_visitor.py
+++ b/pyanalyze/name_check_visitor.py
@@ -2993,7 +2993,10 @@ class NameCheckVisitor(node_visitor.ReplacingNodeVisitor):
 
     def visit_Await(self, node: ast.Await) -> Value:
         composite = self.composite_from_node(node.value)
-        return self.unpack_awaitable(composite, node.value)
+        return_value = self.unpack_awaitable(composite, node.value)
+        if return_value is NO_RETURN_VALUE:
+            self._set_name_in_scope(LEAVES_SCOPE, node, AnyValue(AnySource.marker))
+        return return_value
 
     def unpack_awaitable(self, composite: Composite, node: ast.AST) -> Value:
         tv_map = AwaitableValue.can_assign(composite.value, self)


Test patch:
diff --git a/pyanalyze/test_async_await.py b/pyanalyze/test_async_await.py
index 5a2524c..5bafbd6 100644
--- a/pyanalyze/test_async_await.py
+++ b/pyanalyze/test_async_await.py
@@ -259,3 +259,29 @@ class TestArgSpec(TestNameCheckVisitorBase):
 
         async def g():
             assert_is_value(f(), GenericValue(Awaitable, [KnownValue(42)]))
+
+
+class TestNoReturn(TestNameCheckVisitorBase):
+    @assert_passes()
+    def test(self):
+        from typing import NoReturn
+
+        async def noret() -> NoReturn:
+            raise NotImplementedError
+
+        async def capybara(cond) -> int:
+            if cond:
+                return 3
+            else:
+                await noret()
+
+        async def pacarana(cond) -> int:  # E: missing_return
+            if cond:
+                return 3
+            else:
+                x = noret()
+                print(x)
+
+        async def hutia(cond) -> int:  # E: missing_return
+            if cond:
+                return 3