From bbbdeebcc13281a06a2e582da202062a6857732e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 7 Aug 2026 22:18:19 +0300 Subject: [PATCH] gh-89132: Fix subclassing of annotated types __mro_entries__() of an annotated type now delegates to __mro_entries__() of the annotated type itself if it is not a class, e.g. a generic alias. Subclassing Annotated[X, ...] is now the same as subclassing X. --- Lib/test/test_typing.py | 30 +++++++++++++++++++ Lib/typing.py | 9 +++++- ...6-08-07-03-40-00.gh-issue-89132.annmro.rst | 4 +++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-07-03-40-00.gh-issue-89132.annmro.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2875303fb156197..dbc69d4b134862c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -9943,6 +9943,36 @@ def test_cannot_subclass(self): class C(Annotated): pass + def test_subclass(self): + # gh-89132: subclassing an annotated type is the same as subclassing + # the annotated type itself. + class MyGeneric(Generic[T]): + pass + + for tp in (list, List, List[int], list[int], MyGeneric[int], + collections.abc.Sequence[int]): + with self.subTest(tp=tp): + class C(Annotated[tp, "a decoration"]): + pass + + class D(tp): + pass + + self.assertEqual(C.__bases__, D.__bases__) + self.assertEqual(C.__mro__[1:], D.__mro__[1:]) + + def test_cannot_subclass_not_subclassable(self): + # gh-89132: the error message is the same as for the annotated type. + for tp in (Union[int, str], int | str, T): + with self.subTest(tp=tp): + with self.assertRaises(TypeError) as cm: + class D(tp): + pass + with self.assertRaises(TypeError) as cm2: + class C(Annotated[tp, "a decoration"]): + pass + self.assertEqual(str(cm2.exception), str(cm.exception)) + def test_cannot_check_instance(self): with self.assertRaises(TypeError): isinstance(5, Annotated[int, "positive"]) diff --git a/Lib/typing.py b/Lib/typing.py index b56ba954ad38be1..68e67de8227a5f4 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2254,7 +2254,14 @@ def __getattr__(self, attr): return super().__getattr__(attr) def __mro_entries__(self, bases): - return (self.__origin__,) + origin = self.__origin__ + if not isinstance(origin, type): + # The origin can need a resolution itself, e.g. list[int]. + meth = getattr(origin, '__mro_entries__', None) + if meth is not None: + bases = tuple(origin if b is self else b for b in bases) + return meth(bases) + return (origin,) @_TypedCacheSpecialForm diff --git a/Misc/NEWS.d/next/Library/2026-08-07-03-40-00.gh-issue-89132.annmro.rst b/Misc/NEWS.d/next/Library/2026-08-07-03-40-00.gh-issue-89132.annmro.rst new file mode 100644 index 000000000000000..32173af53fb96a4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-07-03-40-00.gh-issue-89132.annmro.rst @@ -0,0 +1,4 @@ +Subclassing :data:`typing.Annotated` types now works if the annotated type +is a generic alias, e.g. ``Annotated[list[int], "metadata"]``. If the +annotated type cannot be subclassed, the raised error is now the same as for +that type.