mirror of https://github.com/python/cpython
gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() (#115191)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
5a173efa69
commit
a225520af9
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple
|
||||
inheritance with generic aliases (regression in early 3.13 alpha releases).
|
Loading…
Reference in New Issue