Skip to content

Commit

Permalink
review comments (thanks Alex and Serhiy)
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Feb 9, 2024
1 parent 0cee0a6 commit 4e4af1f
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 22 deletions.
67 changes: 51 additions & 16 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4920,39 +4920,74 @@ class B(Generic[S]): ...
class C(List[int], B): ...
self.assertEqual(C.__mro__, (C, list, B, Generic, object))

def test_multiple_inheritance_with_non_type_implementing___mro_entries__(self):
class O:
def test_multiple_inheritance_non_type_with___mro_entries__(self):
class GoodEntries:
def __mro_entries__(self, bases):
return (object,)

class A(List[int], O()): ...
class A(List[int], GoodEntries()): ...

self.assertEqual(A.__mro__, (A, list, Generic, object))

def test_multiple_inheritance_with_non_type_without___mro_entries__(self):
class O: pass
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()): ...

# We should get an error here, but from the type machinery, not from typing.py
with self.assertRaisesRegex(TypeError, r"^metaclass conflict:"):
class A(List[int], O()): ...

def test_multiple_inheritance_with_non_type_with_bad___mro_entries__(self):
class O:
def test_multiple_inheritance_non_type_bad___mro_entries__(self):
class BadEntries:
def __mro_entries__(self, bases):
return None

# We should get an error here, but from the type machinery, not from typing.py
with self.assertRaisesRegex(TypeError, r"^__mro_entries__ must return a tuple"):
class A(List[int], O()): ...
# 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__[:4], (MySeq, list, BaseSeq, collections.abc.Sequence))
self.assertEqual(MySeq.__mro__[-2:], (Generic, object))
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):
Expand Down
21 changes: 16 additions & 5 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,17 +1135,28 @@ def __mro_entries__(self, bases):
res = []
if self.__origin__ not in bases:
res.append(self.__origin__)

# Check if any base that occurs after us in the bases list is either
# itself a subclass of Generic, or something which will add a subclass
# of Generic via its __mro_entries__ (which includes another
# _BaseGenericAlias). If not, add Generic ourselves. The goal is to
# ensure that Generic (or a subclass) will appear exactly once in the
# final bases list. If we let it appear multiple times, we risk "can't
# form a consistent MRO" errors.
i = bases.index(self)
# The goal here is to only add Generic to the MRO if nothing else in the
# MRO is already a subclass of Generic; otherwise we risk failure to
# linearize a consistent MRO.
for b in bases[i+1:]:
if isinstance(b, _BaseGenericAlias):
break
if not isinstance(b, type):
meth = getattr(b, "__mro_entries__", None)
nb = meth(bases) if meth else None
if isinstance(nb, tuple) and any(issubclass(b2, Generic) for b2 in nb):
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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple
inheritance with generic aliases.
inheritance with generic aliases (regression in early 3.13 alpha releases.)

0 comments on commit 4e4af1f

Please sign in to comment.