]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-104935: typing: Fix interactions between `@runtime_checkable` and `Generic...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 25 May 2023 17:15:48 +0000 (10:15 -0700)
committerGitHub <noreply@github.com>
Thu, 25 May 2023 17:15:48 +0000 (17:15 +0000)
gh-104935: typing: Fix interactions between `@runtime_checkable` and `Generic` (GH-104939)

---------

(cherry picked from commit 2b7027d0b2ee2e102a24a0da27d01b8221f9351c)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst [new file with mode: 0644]

index 7fe137f8d7c526038ce0922b643f481e490bf0e3..c8bf94307c8011336425f8718901992a493e3f5c 100644 (file)
@@ -2472,6 +2472,48 @@ class ProtocolTests(BaseTestCase):
         self.assertNotIsSubclass(types.FunctionType, P)
         self.assertNotIsInstance(f, P)
 
+    def test_runtime_checkable_generic_non_protocol(self):
+        # Make sure this doesn't raise AttributeError
+        with self.assertRaisesRegex(
+            TypeError,
+            "@runtime_checkable can be only applied to protocol classes",
+        ):
+            @runtime_checkable
+            class Foo[T]: ...
+
+    def test_runtime_checkable_generic(self):
+        @runtime_checkable
+        class Foo[T](Protocol):
+            def meth(self) -> T: ...
+
+        class Impl:
+            def meth(self) -> int: ...
+
+        self.assertIsSubclass(Impl, Foo)
+
+        class NotImpl:
+            def method(self) -> int: ...
+
+        self.assertNotIsSubclass(NotImpl, Foo)
+
+    def test_pep695_generics_can_be_runtime_checkable(self):
+        @runtime_checkable
+        class HasX(Protocol):
+            x: int
+
+        class Bar[T]:
+            x: T
+            def __init__(self, x):
+                self.x = x
+
+        class Capybara[T]:
+            y: str
+            def __init__(self, y):
+                self.y = y
+
+        self.assertIsInstance(Bar(1), HasX)
+        self.assertNotIsInstance(Capybara('a'), HasX)
+
     def test_everything_implements_empty_protocol(self):
         @runtime_checkable
         class Empty(Protocol):
index 88837db4b744ab594d5be150420d4457fd235294..dd172b17639b33ba61fc92c1837434bf636709cf 100644 (file)
@@ -1899,7 +1899,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
                     annotations = getattr(base, '__annotations__', {})
                     if (isinstance(annotations, collections.abc.Mapping) and
                             attr in annotations and
-                            issubclass(other, Generic) and other._is_protocol):
+                            issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
                         break
                 else:
                     return NotImplemented
@@ -1917,7 +1917,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
             if not (base in (object, Generic) or
                     base.__module__ in _PROTO_ALLOWLIST and
                     base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
-                    issubclass(base, Generic) and base._is_protocol):
+                    issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
                 raise TypeError('Protocols can only inherit from other'
                                 ' protocols, got %r' % base)
         if cls.__init__ is Protocol.__init__:
@@ -2064,7 +2064,7 @@ def runtime_checkable(cls):
     Warning: this will check only the presence of the required methods,
     not their type signatures!
     """
-    if not issubclass(cls, Generic) or not cls._is_protocol:
+    if not issubclass(cls, Generic) or not getattr(cls, '_is_protocol', False):
         raise TypeError('@runtime_checkable can be only applied to protocol classes,'
                         ' got %r' % cls)
     cls._is_runtime_protocol = True
diff --git a/Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst b/Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst
new file mode 100644 (file)
index 0000000..7af52bc
--- /dev/null
@@ -0,0 +1,3 @@
+Fix bugs with the interaction between :func:`typing.runtime_checkable` and
+:class:`typing.Generic` that were introduced by the :pep:`695`
+implementation. Patch by Jelle Zijlstra.