summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2024-02-09 19:19:09 (GMT)
committerGitHub <noreply@github.com>2024-02-09 19:19:09 (GMT)
commita225520af941fb125a4ede77a617501dfb8b46da (patch)
tree51888001d15b176d7c2f60b5db1b95dd74f3d136 /Lib
parent5a173efa693a053bf4a059c82c1c06c82a9fa8fb (diff)
downloadcpython-a225520af941fb125a4ede77a617501dfb8b46da.zip
cpython-a225520af941fb125a4ede77a617501dfb8b46da.tar.gz
cpython-a225520af941fb125a4ede77a617501dfb8b46da.tar.bz2
gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() (#115191)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_typing.py69
-rw-r--r--Lib/typing.py22
2 files changed, 90 insertions, 1 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index b684af4..58566c4 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4920,6 +4920,75 @@ class GenericTests(BaseTestCase):
class C(List[int], B): ...
self.assertEqual(C.__mro__, (C, list, B, Generic, object))
+ def test_multiple_inheritance_non_type_with___mro_entries__(self):
+ class GoodEntries:
+ def __mro_entries__(self, bases):
+ return (object,)
+
+ class A(List[int], GoodEntries()): ...
+
+ self.assertEqual(A.__mro__, (A, list, Generic, object))
+
+ def test_multiple_inheritance_non_type_without___mro_entries__(self):
+ # Error should be from the type machinery, not from typing.py
+ with self.assertRaisesRegex(TypeError, r"^bases must be types"):
+ class A(List[int], object()): ...
+
+ def test_multiple_inheritance_non_type_bad___mro_entries__(self):
+ class BadEntries:
+ def __mro_entries__(self, bases):
+ return None
+
+ # Error should be from the type machinery, not from typing.py
+ with self.assertRaisesRegex(
+ TypeError,
+ r"^__mro_entries__ must return a tuple",
+ ):
+ class A(List[int], BadEntries()): ...
+
+ def test_multiple_inheritance___mro_entries___returns_non_type(self):
+ class BadEntries:
+ def __mro_entries__(self, bases):
+ return (object(),)
+
+ # Error should be from the type machinery, not from typing.py
+ with self.assertRaisesRegex(
+ TypeError,
+ r"^bases must be types",
+ ):
+ class A(List[int], BadEntries()): ...
+
+ def test_multiple_inheritance_with_genericalias(self):
+ class A(typing.Sized, list[int]): ...
+
+ self.assertEqual(
+ A.__mro__,
+ (A, collections.abc.Sized, Generic, list, object),
+ )
+
+ def test_multiple_inheritance_with_genericalias_2(self):
+ T = TypeVar("T")
+
+ class BaseSeq(typing.Sequence[T]): ...
+ class MySeq(List[T], BaseSeq[T]): ...
+
+ self.assertEqual(
+ MySeq.__mro__,
+ (
+ MySeq,
+ list,
+ BaseSeq,
+ collections.abc.Sequence,
+ collections.abc.Reversible,
+ collections.abc.Collection,
+ collections.abc.Sized,
+ collections.abc.Iterable,
+ collections.abc.Container,
+ Generic,
+ object,
+ ),
+ )
+
def test_init_subclass_super_called(self):
class FinalException(Exception):
pass
diff --git a/Lib/typing.py b/Lib/typing.py
index d278b4e..347373f 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1135,9 +1135,29 @@ class _BaseGenericAlias(_Final, _root=True):
res = []
if self.__origin__ not in bases:
res.append(self.__origin__)
+
+ # Check if any base that occurs after us in `bases` is either itself a
+ # subclass of Generic, or something which will add a subclass of Generic
+ # to `__bases__` via its `__mro_entries__`. If not, add Generic
+ # ourselves. The goal is to ensure that Generic (or a subclass) will
+ # appear exactly once in the final bases tuple. If we let it appear
+ # multiple times, we risk "can't form a consistent MRO" errors.
i = bases.index(self)
for b in bases[i+1:]:
- if isinstance(b, _BaseGenericAlias) or issubclass(b, Generic):
+ if isinstance(b, _BaseGenericAlias):
+ break
+ if not isinstance(b, type):
+ meth = getattr(b, "__mro_entries__", None)
+ new_bases = meth(bases) if meth else None
+ if (
+ isinstance(new_bases, tuple) and
+ any(
+ isinstance(b2, type) and issubclass(b2, Generic)
+ for b2 in new_bases
+ )
+ ):
+ break
+ elif issubclass(b, Generic):
break
else:
res.append(Generic)