Skip to content

Commit a35a305

Browse files
gh-112618: Make Annotated cache typed (#112619)
Co-authored-by: Alex Waygood <[email protected]>
1 parent a74daba commit a35a305

File tree

3 files changed

+42
-5
lines changed

3 files changed

+42
-5
lines changed

Lib/test/test_typing.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8675,6 +8675,40 @@ class X(Annotated[int, (1, 10)]): ...
86758675
self.assertEqual(X.__mro__, (X, int, object),
86768676
"Annotated should be transparent.")
86778677

8678+
def test_annotated_cached_with_types(self):
8679+
class A(str): ...
8680+
class B(str): ...
8681+
8682+
field_a1 = Annotated[str, A("X")]
8683+
field_a2 = Annotated[str, B("X")]
8684+
a1_metadata = field_a1.__metadata__[0]
8685+
a2_metadata = field_a2.__metadata__[0]
8686+
8687+
self.assertIs(type(a1_metadata), A)
8688+
self.assertEqual(a1_metadata, A("X"))
8689+
self.assertIs(type(a2_metadata), B)
8690+
self.assertEqual(a2_metadata, B("X"))
8691+
self.assertIsNot(type(a1_metadata), type(a2_metadata))
8692+
8693+
field_b1 = Annotated[str, A("Y")]
8694+
field_b2 = Annotated[str, B("Y")]
8695+
b1_metadata = field_b1.__metadata__[0]
8696+
b2_metadata = field_b2.__metadata__[0]
8697+
8698+
self.assertIs(type(b1_metadata), A)
8699+
self.assertEqual(b1_metadata, A("Y"))
8700+
self.assertIs(type(b2_metadata), B)
8701+
self.assertEqual(b2_metadata, B("Y"))
8702+
self.assertIsNot(type(b1_metadata), type(b2_metadata))
8703+
8704+
field_c1 = Annotated[int, 1]
8705+
field_c2 = Annotated[int, 1.0]
8706+
field_c3 = Annotated[int, True]
8707+
8708+
self.assertIs(type(field_c1.__metadata__[0]), int)
8709+
self.assertIs(type(field_c2.__metadata__[0]), float)
8710+
self.assertIs(type(field_c3.__metadata__[0]), bool)
8711+
86788712

86798713
class TypeAliasTests(BaseTestCase):
86808714
def test_canonical_usage_with_variable_annotation(self):

Lib/typing.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ def __getitem__(self, parameters):
490490
return self._getitem(self, parameters)
491491

492492

493-
class _LiteralSpecialForm(_SpecialForm, _root=True):
493+
class _TypedCacheSpecialForm(_SpecialForm, _root=True):
494494
def __getitem__(self, parameters):
495495
if not isinstance(parameters, tuple):
496496
parameters = (parameters,)
@@ -723,7 +723,7 @@ def Optional(self, parameters):
723723
arg = _type_check(parameters, f"{self} requires a single type.")
724724
return Union[arg, type(None)]
725725

726-
@_LiteralSpecialForm
726+
@_TypedCacheSpecialForm
727727
@_tp_cache(typed=True)
728728
def Literal(self, *parameters):
729729
"""Special typing form to define literal types (a.k.a. value types).
@@ -2005,8 +2005,9 @@ def __mro_entries__(self, bases):
20052005
return (self.__origin__,)
20062006

20072007

2008-
@_SpecialForm
2009-
def Annotated(self, params):
2008+
@_TypedCacheSpecialForm
2009+
@_tp_cache(typed=True)
2010+
def Annotated(self, *params):
20102011
"""Add context-specific metadata to a type.
20112012
20122013
Example: Annotated[int, runtime_check.Unsigned] indicates to the
@@ -2053,7 +2054,7 @@ def Annotated(self, params):
20532054
where T1, T2 etc. are TypeVars, which would be invalid, because
20542055
only one type should be passed to Annotated.
20552056
"""
2056-
if not isinstance(params, tuple) or len(params) < 2:
2057+
if len(params) < 2:
20572058
raise TypeError("Annotated[...] should be used "
20582059
"with at least two arguments (a type and an "
20592060
"annotation).")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a caching bug relating to :data:`typing.Annotated`.
2+
``Annotated[str, True]`` is no longer identical to ``Annotated[str, 1]``.

0 commit comments

Comments
 (0)