From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 25 Jan 2022 14:39:12 +0000 (-0800) Subject: bpo-46491: Allow Annotated on outside of Final/ClassVar (GH-30864) X-Git-Tag: v3.9.11~127 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b0b8388a1c29dc9203dd1a9e8b1420a6a5e88c97;p=thirdparty%2FPython%2Fcpython.git bpo-46491: Allow Annotated on outside of Final/ClassVar (GH-30864) We treat Annotated type arg as class-level annotation. This exempts it from checks against Final and ClassVar in order to allow using them in any nesting order. Automerge-Triggered-By: GH:gvanrossum (cherry picked from commit e1abffca45b60729c460e3e2ad50c8c1946cfd4e) Co-authored-by: Gregory Beauregard --- diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 17da4b81f519..f87832a631d4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4219,6 +4219,14 @@ class AnnotatedTests(BaseTestCase): A.x = 5 self.assertEqual(C.x, 5) + def test_special_form_containment(self): + class C: + classvar: Annotated[ClassVar[int], "a decoration"] = 4 + const: Annotated[Final[int], "Const"] = 4 + + self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())['const'], Final[int]) + def test_hash_eq(self): self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) diff --git a/Lib/typing.py b/Lib/typing.py index da70d4115fa2..fdc0f163d650 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -134,7 +134,7 @@ def _type_convert(arg, module=None): return arg -def _type_check(arg, msg, is_argument=True, module=None, *, is_class=False): +def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=False): """Check that the argument is a type, and return it (internal helper). As a special case, accept None and return type(None) instead. Also wrap strings @@ -147,7 +147,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, is_class=False): We append the repr() of the actual value (truncated to 100 chars). """ invalid_generic_forms = (Generic, Protocol) - if not is_class: + if not allow_special_forms: invalid_generic_forms += (ClassVar,) if is_argument: invalid_generic_forms += (Final,) @@ -554,7 +554,7 @@ class ForwardRef(_Final, _root=True): eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__, - is_class=self.__forward_is_class__, + allow_special_forms=self.__forward_is_class__, ) self.__forward_value__ = _eval_type( type_, globalns, localns, recursive_guard | {self.__forward_arg__} @@ -1336,7 +1336,7 @@ class Annotated: "with at least two arguments (a type and an " "annotation).") msg = "Annotated[t, ...]: t must be a type." - origin = _type_check(params[0], msg) + origin = _type_check(params[0], msg, allow_special_forms=True) metadata = tuple(params[1:]) return _AnnotatedAlias(origin, metadata) diff --git a/Misc/NEWS.d/next/Library/2022-01-24-23-55-30.bpo-46491.jmIKHo.rst b/Misc/NEWS.d/next/Library/2022-01-24-23-55-30.bpo-46491.jmIKHo.rst new file mode 100644 index 000000000000..f66e8868f753 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-24-23-55-30.bpo-46491.jmIKHo.rst @@ -0,0 +1 @@ +Allow :data:`typing.Annotated` to wrap :data:`typing.Final` and :data:`typing.ClassVar`. Patch by Gregory Beauregard.