From 4e4af1fdcf056751bc8a41e30008acd40e0b4c6d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 9 Feb 2024 08:06:45 -0800 Subject: [PATCH] review comments (thanks Alex and Serhiy) --- Lib/test/test_typing.py | 67 ++++++++++++++----- Lib/typing.py | 21 ++++-- ...-02-08-17-04-58.gh-issue-112903.SN_vUs.rst | 2 +- 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 9611a618da4eb0..58566c4bfc821c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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): diff --git a/Lib/typing.py b/Lib/typing.py index 3e533fb0f56cfd..d9b86a68896aab 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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 diff --git a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst index 1d65e767551008..cdb2b06eff6000 100644 --- a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst +++ b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst @@ -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.)