From d308d33e098d8e176f1e5169225d3cf800ed6aa1 Mon Sep 17 00:00:00 2001 From: Mehdi Drissi Date: Mon, 11 Mar 2024 23:11:56 -0700 Subject: [PATCH] gh-89547: Support for nesting special forms like Final (#116096) --- Lib/test/test_typing.py | 40 +++++++++++++------ Lib/typing.py | 4 +- Misc/ACKS | 1 + ...4-02-28-17-50-42.gh-issue-89547.GetF38.rst | 1 + 4 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-28-17-50-42.gh-issue-89547.GetF38.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 912384ab6bfe84..a9942b44f29ed9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4655,8 +4655,6 @@ def test_fail_with_bare_union(self): List[Union] with self.assertRaises(TypeError): Tuple[Optional] - with self.assertRaises(TypeError): - ClassVar[ClassVar[int]] with self.assertRaises(TypeError): List[ClassVar[int]] @@ -6014,16 +6012,6 @@ class F: for clazz in [C, D, E, F]: self.assertEqual(get_type_hints(clazz), expected_result) - def test_nested_classvar_fails_forward_ref_check(self): - class E: - foo: 'typing.ClassVar[typing.ClassVar[int]]' = 7 - class F: - foo: ClassVar['ClassVar[int]'] = 7 - - for clazz in [E, F]: - with self.assertRaises(TypeError): - get_type_hints(clazz) - def test_meta_no_type_check(self): depr_msg = ( "'typing.no_type_check_decorator' is deprecated " @@ -8716,6 +8704,34 @@ class C: self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int]) self.assertEqual(get_type_hints(C, globals())['const'], Final[int]) + def test_special_forms_nesting(self): + # These are uncommon types and are to ensure runtime + # is lax on validation. See gh-89547 for more context. + class CF: + x: ClassVar[Final[int]] + + class FC: + x: Final[ClassVar[int]] + + class ACF: + x: Annotated[ClassVar[Final[int]], "a decoration"] + + class CAF: + x: ClassVar[Annotated[Final[int], "a decoration"]] + + class AFC: + x: Annotated[Final[ClassVar[int]], "a decoration"] + + class FAC: + x: Final[Annotated[ClassVar[int], "a decoration"]] + + self.assertEqual(get_type_hints(CF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(FC, globals())['x'], Final[ClassVar[int]]) + self.assertEqual(get_type_hints(ACF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(CAF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(AFC, globals())['x'], Final[ClassVar[int]]) + self.assertEqual(get_type_hints(FAC, globals())['x'], Final[ClassVar[int]]) + def test_cannot_subclass(self): with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): class C(Annotated): diff --git a/Lib/typing.py b/Lib/typing.py index cca9525d632ea5..b2350433953ccd 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -653,7 +653,7 @@ class Starship: Note that ClassVar is not a class itself, and should not be used with isinstance() or issubclass(). """ - item = _type_check(parameters, f'{self} accepts only single type.') + item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) @_SpecialForm @@ -675,7 +675,7 @@ class FastConnector(Connection): There is no runtime checking of these properties. """ - item = _type_check(parameters, f'{self} accepts only single type.') + item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) @_SpecialForm diff --git a/Misc/ACKS b/Misc/ACKS index f01c7a70a65dc5..03e458d170d2b8 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -470,6 +470,7 @@ Allen Downey Cesar Douady Dean Draayer Fred L. Drake, Jr. +Mehdi Drissi Derk Drukker John DuBois Paul Dubois diff --git a/Misc/NEWS.d/next/Library/2024-02-28-17-50-42.gh-issue-89547.GetF38.rst b/Misc/NEWS.d/next/Library/2024-02-28-17-50-42.gh-issue-89547.GetF38.rst new file mode 100644 index 00000000000000..7be4591b83a8f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-28-17-50-42.gh-issue-89547.GetF38.rst @@ -0,0 +1 @@ +Add support for nested typing special forms like Final[ClassVar[int]].