From bcb274c9d47525db7e5d69c282ff970dffe51282 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 2 Dec 2023 12:56:00 +0300 Subject: [PATCH 1/9] gh-112618: Make `Annotated` cache typed --- Lib/test/test_typing.py | 27 +++++++++++++++++++ Lib/typing.py | 14 +++++++--- ...-12-02-12-55-17.gh-issue-112618.7_FT8-.rst | 2 ++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4fbb410f26ab8d..0e6a8413f04eed 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8675,6 +8675,33 @@ class X(Annotated[int, (1, 10)]): ... self.assertEqual(X.__mro__, (X, int, object), "Annotated should be transparent.") + def test_annotated_cached_with_types(self): + class A(str): ... + class B(str): ... + + class Message: + field_a: Annotated[str, A("X")] + field_b: Annotated[str, A("Y")] + field_c: Annotated[int, 1] + class Form: + box_a: Annotated[str, B("X")] + box_b: Annotated[str, B("Y")] + box_c: Annotated[int, 1.0] + + hints = get_type_hints(Message, include_extras=True) + self.assertEqual(type(hints["field_a"].__metadata__[0]), A) + self.assertEqual(hints["field_a"].__metadata__[0], A("X")) + self.assertEqual(type(hints["field_b"].__metadata__[0]), A) + self.assertEqual(hints["field_b"].__metadata__[0], A("Y")) + self.assertEqual(type(hints["field_c"].__metadata__[0]), int) + + hints = get_type_hints(Form, include_extras=True) + self.assertEqual(type(hints["box_a"].__metadata__[0]), B) + self.assertEqual(hints["box_a"].__metadata__[0], B("X")) + self.assertEqual(type(hints["box_b"].__metadata__[0]), B) + self.assertEqual(hints["box_b"].__metadata__[0], B("Y")) + self.assertEqual(type(hints["box_c"].__metadata__[0]), float) + class TypeAliasTests(BaseTestCase): def test_canonical_usage_with_variable_annotation(self): diff --git a/Lib/typing.py b/Lib/typing.py index b3af701f8d5437..9d10245a7e8fc1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -497,6 +497,13 @@ def __getitem__(self, parameters): return self._getitem(self, *parameters) +class _AnnotatedSpecialForm(_SpecialForm, _root=True): + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + return self._getitem(self, *parameters) + + class _AnyMeta(type): def __instancecheck__(self, obj): if self is Any: @@ -2005,8 +2012,9 @@ def __mro_entries__(self, bases): return (self.__origin__,) -@_SpecialForm -def Annotated(self, params): +@_AnnotatedSpecialForm +@_tp_cache(typed=True) +def Annotated(self, *params): """Add context-specific metadata to a type. Example: Annotated[int, runtime_check.Unsigned] indicates to the @@ -2053,7 +2061,7 @@ def Annotated(self, params): where T1, T2 etc. are TypeVars, which would be invalid, because only one type should be passed to Annotated. """ - if not isinstance(params, tuple) or len(params) < 2: + if len(params) < 2: raise TypeError("Annotated[...] should be used " "with at least two arguments (a type and an " "annotation).") diff --git a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst new file mode 100644 index 00000000000000..b3203a8ec1730f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst @@ -0,0 +1,2 @@ +Make ``typing.Annotated`` cache work different for different types. Now +``Annoated[int, 1]`` and ``Annotated[int, 1.0]`` will be different. From 3b573022334e6eb230a9d589d2cba031eef9a146 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 12:58:58 +0300 Subject: [PATCH 2/9] Update 2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst --- .../next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst index b3203a8ec1730f..60ed6cab8b5b2b 100644 --- a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst +++ b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst @@ -1,2 +1,2 @@ Make ``typing.Annotated`` cache work different for different types. Now -``Annoated[int, 1]`` and ``Annotated[int, 1.0]`` will be different. +``Annotated[int, 1]`` and ``Annotated[int, 1.0]`` will be different. From 70895b4d6d51e6da0f84d0f86dc9dfed00730971 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 15:58:55 +0300 Subject: [PATCH 3/9] Update Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst Co-authored-by: Alex Waygood --- .../Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst index 60ed6cab8b5b2b..89b4c0b49d3db5 100644 --- a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst +++ b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst @@ -1,2 +1,2 @@ -Make ``typing.Annotated`` cache work different for different types. Now -``Annotated[int, 1]`` and ``Annotated[int, 1.0]`` will be different. +Fix a caching bug relating to :data:`typing.Annotated`. +``Annotated[str, True]`` is no longer equal to ``Annotated[str, 1]``. From 03b43b3215dc80cd7756e6e3b9a85b9c84d4771b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 2 Dec 2023 16:04:58 +0300 Subject: [PATCH 4/9] Rename type --- Lib/typing.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 9d10245a7e8fc1..4c19aadabe3b87 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -490,14 +490,7 @@ def __getitem__(self, parameters): return self._getitem(self, parameters) -class _LiteralSpecialForm(_SpecialForm, _root=True): - def __getitem__(self, parameters): - if not isinstance(parameters, tuple): - parameters = (parameters,) - return self._getitem(self, *parameters) - - -class _AnnotatedSpecialForm(_SpecialForm, _root=True): +class _TypedCacheSpecialForm(_SpecialForm, _root=True): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) @@ -730,7 +723,7 @@ def Optional(self, parameters): arg = _type_check(parameters, f"{self} requires a single type.") return Union[arg, type(None)] -@_LiteralSpecialForm +@_TypedCacheSpecialForm @_tp_cache(typed=True) def Literal(self, *parameters): """Special typing form to define literal types (a.k.a. value types). @@ -2012,7 +2005,7 @@ def __mro_entries__(self, bases): return (self.__origin__,) -@_AnnotatedSpecialForm +@_TypedCacheSpecialForm @_tp_cache(typed=True) def Annotated(self, *params): """Add context-specific metadata to a type. From b9a0c3398fee0eb2396f15869b44392a817bffbf Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 2 Dec 2023 17:05:53 +0300 Subject: [PATCH 5/9] Address review --- Lib/test/test_typing.py | 45 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0e6a8413f04eed..3221c6216942f9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8679,28 +8679,29 @@ def test_annotated_cached_with_types(self): class A(str): ... class B(str): ... - class Message: - field_a: Annotated[str, A("X")] - field_b: Annotated[str, A("Y")] - field_c: Annotated[int, 1] - class Form: - box_a: Annotated[str, B("X")] - box_b: Annotated[str, B("Y")] - box_c: Annotated[int, 1.0] - - hints = get_type_hints(Message, include_extras=True) - self.assertEqual(type(hints["field_a"].__metadata__[0]), A) - self.assertEqual(hints["field_a"].__metadata__[0], A("X")) - self.assertEqual(type(hints["field_b"].__metadata__[0]), A) - self.assertEqual(hints["field_b"].__metadata__[0], A("Y")) - self.assertEqual(type(hints["field_c"].__metadata__[0]), int) - - hints = get_type_hints(Form, include_extras=True) - self.assertEqual(type(hints["box_a"].__metadata__[0]), B) - self.assertEqual(hints["box_a"].__metadata__[0], B("X")) - self.assertEqual(type(hints["box_b"].__metadata__[0]), B) - self.assertEqual(hints["box_b"].__metadata__[0], B("Y")) - self.assertEqual(type(hints["box_c"].__metadata__[0]), float) + field_a1 = Annotated[str, A("X")] + field_a2 = Annotated[str, B("X")] + + self.assertEqual(type(field_a1.__metadata__[0]), A) + self.assertEqual(field_a1.__metadata__[0], A("X")) + self.assertEqual(type(field_a2.__metadata__[0]), B) + self.assertEqual(field_a2.__metadata__[0], B("X")) + + field_b1 = Annotated[str, A("Y")] + field_b2 = Annotated[str, B("Y")] + + self.assertEqual(type(field_b1.__metadata__[0]), A) + self.assertEqual(field_b1.__metadata__[0], A("Y")) + self.assertEqual(type(field_b2.__metadata__[0]), B) + self.assertEqual(field_b2.__metadata__[0], B("Y")) + + field_c1 = Annotated[int, 1] + field_c2 = Annotated[int, 1.0] + field_c3 = Annotated[int, True] + + self.assertEqual(type(field_c1.__metadata__[0]), int) + self.assertEqual(type(field_c2.__metadata__[0]), float) + self.assertEqual(type(field_c3.__metadata__[0]), bool) class TypeAliasTests(BaseTestCase): From 13eb1926f7bcaa9fa843e31c8af8983746d22a04 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 2 Dec 2023 17:12:33 +0300 Subject: [PATCH 6/9] Address review --- Lib/test/test_typing.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3221c6216942f9..27a24b60ffbe3a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8682,26 +8682,26 @@ class B(str): ... field_a1 = Annotated[str, A("X")] field_a2 = Annotated[str, B("X")] - self.assertEqual(type(field_a1.__metadata__[0]), A) + self.assertIs(type(field_a1.__metadata__[0]), A) self.assertEqual(field_a1.__metadata__[0], A("X")) - self.assertEqual(type(field_a2.__metadata__[0]), B) + self.assertIs(type(field_a2.__metadata__[0]), B) self.assertEqual(field_a2.__metadata__[0], B("X")) field_b1 = Annotated[str, A("Y")] field_b2 = Annotated[str, B("Y")] - self.assertEqual(type(field_b1.__metadata__[0]), A) + self.assertIs(type(field_b1.__metadata__[0]), A) self.assertEqual(field_b1.__metadata__[0], A("Y")) - self.assertEqual(type(field_b2.__metadata__[0]), B) + self.assertIs(type(field_b2.__metadata__[0]), B) self.assertEqual(field_b2.__metadata__[0], B("Y")) field_c1 = Annotated[int, 1] field_c2 = Annotated[int, 1.0] field_c3 = Annotated[int, True] - self.assertEqual(type(field_c1.__metadata__[0]), int) - self.assertEqual(type(field_c2.__metadata__[0]), float) - self.assertEqual(type(field_c3.__metadata__[0]), bool) + self.assertIs(type(field_c1.__metadata__[0]), int) + self.assertIs(type(field_c2.__metadata__[0]), float) + self.assertIs(type(field_c3.__metadata__[0]), bool) class TypeAliasTests(BaseTestCase): From 0a842a0307003edd54821b13f2fb5109593470ab Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 17:23:37 +0300 Subject: [PATCH 7/9] Update 2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst --- .../next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst index 89b4c0b49d3db5..c732de15609c96 100644 --- a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst +++ b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst @@ -1,2 +1,2 @@ Fix a caching bug relating to :data:`typing.Annotated`. -``Annotated[str, True]`` is no longer equal to ``Annotated[str, 1]``. +``Annotated[str, True]`` is no longer identical to ``Annotated[str, 1]``. From b83154b5db35361fd898bb63d5635103611e3ce2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 17:48:13 +0300 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 27a24b60ffbe3a..0305495235ac6a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8681,19 +8681,25 @@ class B(str): ... field_a1 = Annotated[str, A("X")] field_a2 = Annotated[str, B("X")] + a1_metadata = field_a1.__metadata__[0] + a2_metadata = field_a2.__metadata__[0] - self.assertIs(type(field_a1.__metadata__[0]), A) - self.assertEqual(field_a1.__metadata__[0], A("X")) - self.assertIs(type(field_a2.__metadata__[0]), B) - self.assertEqual(field_a2.__metadata__[0], B("X")) + self.assertIs(type(a1_metadata), A) + self.assertEqual(a1_metadata, A("X")) + self.assertIs(type(a2_metadata, B) + self.assertEqual(a2_metadata, B("X")) + self.assertIsNot(type(a1_metadata), type(a2_metadata)) field_b1 = Annotated[str, A("Y")] field_b2 = Annotated[str, B("Y")] - - self.assertIs(type(field_b1.__metadata__[0]), A) - self.assertEqual(field_b1.__metadata__[0], A("Y")) - self.assertIs(type(field_b2.__metadata__[0]), B) - self.assertEqual(field_b2.__metadata__[0], B("Y")) + b1_metadata = field_b1.__metadata__[0] + b2_metadata = field_b2.__metadata__[0] + + self.assertIs(type(b1_metadata), A) + self.assertEqual(b1_metadata, A("Y")) + self.assertIs(type(b2_metadata), B) + self.assertEqual(b2_metadata, B("Y")) + self.assertIsNot(type(b1_metadata), type(b2_metadata)) field_c1 = Annotated[int, 1] field_c2 = Annotated[int, 1.0] From 5025f1c406374090f273db344e6ff784466690a8 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 2 Dec 2023 14:51:19 +0000 Subject: [PATCH 9/9] fix syntaxerror --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0305495235ac6a..3572df7737f652 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8686,7 +8686,7 @@ class B(str): ... self.assertIs(type(a1_metadata), A) self.assertEqual(a1_metadata, A("X")) - self.assertIs(type(a2_metadata, B) + self.assertIs(type(a2_metadata), B) self.assertEqual(a2_metadata, B("X")) self.assertIsNot(type(a1_metadata), type(a2_metadata))