diff options
author | Carl Meyer <carl@oddbird.net> | 2024-02-09 19:19:09 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-09 19:19:09 (GMT) |
commit | a225520af941fb125a4ede77a617501dfb8b46da (patch) | |
tree | 51888001d15b176d7c2f60b5db1b95dd74f3d136 /Lib | |
parent | 5a173efa693a053bf4a059c82c1c06c82a9fa8fb (diff) | |
download | cpython-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.py | 69 | ||||
-rw-r--r-- | Lib/typing.py | 22 |
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) |