Skip to content

Commit a225520

Browse files
carljmAlexWaygood
andauthored
gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() (#115191)
Co-authored-by: Alex Waygood <[email protected]>
1 parent 5a173ef commit a225520

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

Lib/test/test_typing.py

+69
Original file line numberDiff line numberDiff line change
@@ -4920,6 +4920,75 @@ class B(Generic[S]): ...
49204920
class C(List[int], B): ...
49214921
self.assertEqual(C.__mro__, (C, list, B, Generic, object))
49224922

4923+
def test_multiple_inheritance_non_type_with___mro_entries__(self):
4924+
class GoodEntries:
4925+
def __mro_entries__(self, bases):
4926+
return (object,)
4927+
4928+
class A(List[int], GoodEntries()): ...
4929+
4930+
self.assertEqual(A.__mro__, (A, list, Generic, object))
4931+
4932+
def test_multiple_inheritance_non_type_without___mro_entries__(self):
4933+
# Error should be from the type machinery, not from typing.py
4934+
with self.assertRaisesRegex(TypeError, r"^bases must be types"):
4935+
class A(List[int], object()): ...
4936+
4937+
def test_multiple_inheritance_non_type_bad___mro_entries__(self):
4938+
class BadEntries:
4939+
def __mro_entries__(self, bases):
4940+
return None
4941+
4942+
# Error should be from the type machinery, not from typing.py
4943+
with self.assertRaisesRegex(
4944+
TypeError,
4945+
r"^__mro_entries__ must return a tuple",
4946+
):
4947+
class A(List[int], BadEntries()): ...
4948+
4949+
def test_multiple_inheritance___mro_entries___returns_non_type(self):
4950+
class BadEntries:
4951+
def __mro_entries__(self, bases):
4952+
return (object(),)
4953+
4954+
# Error should be from the type machinery, not from typing.py
4955+
with self.assertRaisesRegex(
4956+
TypeError,
4957+
r"^bases must be types",
4958+
):
4959+
class A(List[int], BadEntries()): ...
4960+
4961+
def test_multiple_inheritance_with_genericalias(self):
4962+
class A(typing.Sized, list[int]): ...
4963+
4964+
self.assertEqual(
4965+
A.__mro__,
4966+
(A, collections.abc.Sized, Generic, list, object),
4967+
)
4968+
4969+
def test_multiple_inheritance_with_genericalias_2(self):
4970+
T = TypeVar("T")
4971+
4972+
class BaseSeq(typing.Sequence[T]): ...
4973+
class MySeq(List[T], BaseSeq[T]): ...
4974+
4975+
self.assertEqual(
4976+
MySeq.__mro__,
4977+
(
4978+
MySeq,
4979+
list,
4980+
BaseSeq,
4981+
collections.abc.Sequence,
4982+
collections.abc.Reversible,
4983+
collections.abc.Collection,
4984+
collections.abc.Sized,
4985+
collections.abc.Iterable,
4986+
collections.abc.Container,
4987+
Generic,
4988+
object,
4989+
),
4990+
)
4991+
49234992
def test_init_subclass_super_called(self):
49244993
class FinalException(Exception):
49254994
pass

Lib/typing.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1135,9 +1135,29 @@ def __mro_entries__(self, bases):
11351135
res = []
11361136
if self.__origin__ not in bases:
11371137
res.append(self.__origin__)
1138+
1139+
# Check if any base that occurs after us in `bases` is either itself a
1140+
# subclass of Generic, or something which will add a subclass of Generic
1141+
# to `__bases__` via its `__mro_entries__`. If not, add Generic
1142+
# ourselves. The goal is to ensure that Generic (or a subclass) will
1143+
# appear exactly once in the final bases tuple. If we let it appear
1144+
# multiple times, we risk "can't form a consistent MRO" errors.
11381145
i = bases.index(self)
11391146
for b in bases[i+1:]:
1140-
if isinstance(b, _BaseGenericAlias) or issubclass(b, Generic):
1147+
if isinstance(b, _BaseGenericAlias):
1148+
break
1149+
if not isinstance(b, type):
1150+
meth = getattr(b, "__mro_entries__", None)
1151+
new_bases = meth(bases) if meth else None
1152+
if (
1153+
isinstance(new_bases, tuple) and
1154+
any(
1155+
isinstance(b2, type) and issubclass(b2, Generic)
1156+
for b2 in new_bases
1157+
)
1158+
):
1159+
break
1160+
elif issubclass(b, Generic):
11411161
break
11421162
else:
11431163
res.append(Generic)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple
2+
inheritance with generic aliases (regression in early 3.13 alpha releases).

0 commit comments

Comments
 (0)