Issue #24400: Resurrect inspect.isawaitable()

collections.abc.Awaitable and collections.abc.Coroutine no longer
use __instancecheck__ hook to detect generator-based coroutines.

inspect.isawaitable() can be used to detect generator-based coroutines
and to distinguish them from regular generator objects.
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index ba6a9b8..f89bb6f 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -81,22 +81,7 @@
         return NotImplemented
 
 
-class _AwaitableMeta(ABCMeta):
-
-    def __instancecheck__(cls, instance):
-        # This hook is needed because we can't add
-        # '__await__' method to generator objects, and
-        # we can't register GeneratorType on Awaitable.
-        # NB: 0x100 = CO_ITERABLE_COROUTINE
-        # (We don't want to import 'inspect' module, as
-        # a dependency for 'collections.abc')
-        if (instance.__class__ is generator and
-            instance.gi_code.co_flags & 0x100):
-            return True
-        return super().__instancecheck__(instance)
-
-
-class Awaitable(metaclass=_AwaitableMeta):
+class Awaitable(metaclass=ABCMeta):
 
     __slots__ = ()
 
diff --git a/Lib/inspect.py b/Lib/inspect.py
index f48769e..45679cf 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -207,6 +207,13 @@
     """Return true if the object is a coroutine."""
     return isinstance(object, types.CoroutineType)
 
+def isawaitable(object):
+    """Return true is object can be passed to an ``await`` expression."""
+    return (isinstance(object, types.CoroutineType) or
+            isinstance(object, types.GeneratorType) and
+                object.gi_code.co_flags & CO_ITERABLE_COROUTINE or
+            isinstance(object, collections.abc.Awaitable))
+
 def istraceback(object):
     """Return true if the object is a traceback.
 
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index ab2b733..fbaf712 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -511,8 +511,10 @@
             self.assertTrue(issubclass(type(x), Awaitable))
 
         c = coro()
-        self.assertIsInstance(c, Awaitable)
-        c.close() # awoid RuntimeWarning that coro() was not awaited
+        # Iterable coroutines (generators with CO_ITERABLE_COROUTINE
+        # flag don't have '__await__' method, hence can't be instances
+        # of Awaitable. Use inspect.isawaitable to detect them.
+        self.assertNotIsInstance(c, Awaitable)
 
         c = new_coro()
         self.assertIsInstance(c, Awaitable)
@@ -559,8 +561,10 @@
             self.assertTrue(issubclass(type(x), Awaitable))
 
         c = coro()
-        self.assertIsInstance(c, Coroutine)
-        c.close() # awoid RuntimeWarning that coro() was not awaited
+        # Iterable coroutines (generators with CO_ITERABLE_COROUTINE
+        # flag don't have '__await__' method, hence can't be instances
+        # of Coroutine. Use inspect.isawaitable to detect them.
+        self.assertNotIsInstance(c, Coroutine)
 
         c = new_coro()
         self.assertIsInstance(c, Coroutine)
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index ab22c7d..a02f2e1 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -151,6 +151,29 @@
 
         coro.close(); gen_coro.close() # silence warnings
 
+    def test_isawaitable(self):
+        def gen(): yield
+        self.assertFalse(inspect.isawaitable(gen()))
+
+        coro = coroutine_function_example(1)
+        gen_coro = gen_coroutine_function_example(1)
+
+        self.assertTrue(inspect.isawaitable(coro))
+        self.assertTrue(inspect.isawaitable(gen_coro))
+
+        class Future:
+            def __await__():
+                pass
+        self.assertTrue(inspect.isawaitable(Future()))
+        self.assertFalse(inspect.isawaitable(Future))
+
+        class NotFuture: pass
+        not_fut = NotFuture()
+        not_fut.__await__ = lambda: None
+        self.assertFalse(inspect.isawaitable(not_fut))
+
+        coro.close(); gen_coro.close() # silence warnings
+
     def test_isroutine(self):
         self.assertTrue(inspect.isroutine(mod.spam))
         self.assertTrue(inspect.isroutine([].count))
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index ba8a1b9..738588e 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1447,6 +1447,19 @@
         with self.assertRaisesRegex(Exception, 'ham'):
             wrapper.throw(Exception, Exception('ham'))
 
+    def test_returning_itercoro(self):
+        @types.coroutine
+        def gen():
+            yield
+
+        gencoro = gen()
+
+        @types.coroutine
+        def foo():
+            return gencoro
+
+        self.assertIs(foo(), gencoro)
+
     def test_genfunc(self):
         def gen(): yield
         self.assertIs(types.coroutine(gen), gen)
@@ -1457,9 +1470,6 @@
         g = gen()
         self.assertTrue(g.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE)
         self.assertFalse(g.gi_code.co_flags & inspect.CO_COROUTINE)
-        self.assertIsInstance(g, collections.abc.Coroutine)
-        self.assertIsInstance(g, collections.abc.Awaitable)
-        g.close() # silence warning
 
         self.assertIs(types.coroutine(gen), gen)
 
diff --git a/Lib/types.py b/Lib/types.py
index 8c5fc65..48891cd 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -241,12 +241,12 @@
     @_functools.wraps(func)
     def wrapped(*args, **kwargs):
         coro = func(*args, **kwargs)
-        if coro.__class__ is CoroutineType:
-            # 'coro' is a native coroutine object.
+        if (coro.__class__ is CoroutineType or
+            coro.__class__ is GeneratorType and coro.gi_code.co_flags & 0x100):
+            # 'coro' is a native coroutine object or an iterable coroutine
             return coro
-        if (coro.__class__ is GeneratorType or
-                (isinstance(coro, _collections_abc.Generator) and
-                 not isinstance(coro, _collections_abc.Coroutine))):
+        if (isinstance(coro, _collections_abc.Generator) and
+            not isinstance(coro, _collections_abc.Coroutine)):
             # 'coro' is either a pure Python generator iterator, or it
             # implements collections.abc.Generator (and does not implement
             # collections.abc.Coroutine).