From c416507f27d8ecfee019183ea13b0b1b15a25410 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 7 May 2024 11:24:27 +0100 Subject: [PATCH 1/3] gh-118418: Deprecate failing to pass a value to the *type_params* parameter of some private `typing` APIs --- Lib/test/test_typing.py | 25 +++++++++++ Lib/typing.py | 41 ++++++++++++++----- ...-05-07-11-23-11.gh-issue-118418.QPMdJm.rst | 6 +++ 3 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-05-07-11-23-11.gh-issue-118418.QPMdJm.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 8f0be1fd3f55e5..fa8d992233e2cb 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6308,6 +6308,31 @@ def test_or(self): self.assertEqual(X | "x", Union[X, "x"]) self.assertEqual("x" | X, Union["x", X]) + def test_deprecation_for_no_type_params_passed_to__evaluate(self): + with self.assertWarnsRegex( + DeprecationWarning, + ( + "Failing to pass a value to the `type_params` parameter " + "of 'typing._eval_type' is deprecated" + ) + ) as cm: + self.assertEqual(typing._eval_type(list["int"], globals(), {}), list[int]) + + self.assertEqual(cm.filename, __file__) + + f = ForwardRef("int") + + with self.assertWarnsRegex( + DeprecationWarning, + ( + "Failing to pass a value to the `type_params` parameter " + "of 'typing.ForwardRef._evaluate' is deprecated" + ) + ) as cm: + self.assertIs(f._evaluate(globals(), {}, recursive_guard=frozenset()), int) + + self.assertEqual(cm.filename, __file__) + @lru_cache() def cached_func(x, y): diff --git a/Lib/typing.py b/Lib/typing.py index c159fcfda68ee8..95867ec74a6d15 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -437,13 +437,38 @@ def inner(*args, **kwds): return decorator -def _eval_type(t, globalns, localns, type_params=None, *, recursive_guard=frozenset()): +def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None: + import warnings + + depr_message = ( + f"Failing to pass a value to the `type_params` parameter " + f"of {funcname!r} is deprecated, as it leads to incorrect behaviour " + f"when calling {funcname} on a stringified annotation " + f"that references a PEP-695 type parameter. " + f"It will be disallowed in Python 3.15." + ) + warnings.warn(depr_message, category=DeprecationWarning, stacklevel=3) + + +class _Sentinel: + __slots__ = () + def __repr__(self): + return '' + + +_sentinel = _Sentinel() + + +def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset()): """Evaluate all forward references in the given type t. For use of globalns and localns see the docstring for get_type_hints(). recursive_guard is used to prevent infinite recursion with a recursive ForwardRef. """ + if type_params is _sentinel: + _deprecation_warning_for_no_type_params_passed("typing._eval_type") + type_params = () if isinstance(t, ForwardRef): return t._evaluate(globalns, localns, type_params, recursive_guard=recursive_guard) if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): @@ -1018,7 +1043,10 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False): self.__forward_is_class__ = is_class self.__forward_module__ = module - def _evaluate(self, globalns, localns, type_params=None, *, recursive_guard): + def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard): + if type_params is _sentinel: + _deprecation_warning_for_no_type_params_passed("typing.ForwardRef._evaluate") + type_params = () if self.__forward_arg__ in recursive_guard: return self if not self.__forward_evaluated__ or localns is not globalns: @@ -2998,15 +3026,6 @@ def __new__(cls, typename, bases, ns): return nm_tpl -class _Sentinel: - __slots__ = () - def __repr__(self): - return '' - - -_sentinel = _Sentinel() - - def NamedTuple(typename, fields=_sentinel, /, **kwargs): """Typed version of namedtuple. diff --git a/Misc/NEWS.d/next/Library/2024-05-07-11-23-11.gh-issue-118418.QPMdJm.rst b/Misc/NEWS.d/next/Library/2024-05-07-11-23-11.gh-issue-118418.QPMdJm.rst new file mode 100644 index 00000000000000..be371c507cbf79 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-07-11-23-11.gh-issue-118418.QPMdJm.rst @@ -0,0 +1,6 @@ +A :exc:`DeprecationWarning` is now emitted if you fail to pass a value to +the new *type_params* parameter of ``typing._eval_type()`` or +``typing.ForwardRef._evaluate()``. (Using either of these private and +undocumented functions is discouraged to begin with, but failing to pass a +value to the ``type_params`` parameter may lead to incorrect behaviour on +Python 3.12 or newer.) From d47c6c4f22b988675c9b2d4bcf5bceef0193e450 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 7 May 2024 11:40:54 +0100 Subject: [PATCH 2/3] no backticks --- Lib/test/test_typing.py | 4 ++-- Lib/typing.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index fa8d992233e2cb..012613302d1b53 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6312,7 +6312,7 @@ def test_deprecation_for_no_type_params_passed_to__evaluate(self): with self.assertWarnsRegex( DeprecationWarning, ( - "Failing to pass a value to the `type_params` parameter " + "Failing to pass a value to the 'type_params' parameter " "of 'typing._eval_type' is deprecated" ) ) as cm: @@ -6325,7 +6325,7 @@ def test_deprecation_for_no_type_params_passed_to__evaluate(self): with self.assertWarnsRegex( DeprecationWarning, ( - "Failing to pass a value to the `type_params` parameter " + "Failing to pass a value to the 'type_params' parameter " "of 'typing.ForwardRef._evaluate' is deprecated" ) ) as cm: diff --git a/Lib/typing.py b/Lib/typing.py index 95867ec74a6d15..7d89de66202ffb 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -441,7 +441,7 @@ def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None: import warnings depr_message = ( - f"Failing to pass a value to the `type_params` parameter " + f"Failing to pass a value to the 'type_params' parameter " f"of {funcname!r} is deprecated, as it leads to incorrect behaviour " f"when calling {funcname} on a stringified annotation " f"that references a PEP-695 type parameter. " From bf3bf8b183a8a130edd1f2c361da4d1ac57447c1 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 7 May 2024 11:45:19 +0100 Subject: [PATCH 3/3] no hyphen --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 7d89de66202ffb..e48583673a7be2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -444,7 +444,7 @@ def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None: f"Failing to pass a value to the 'type_params' parameter " f"of {funcname!r} is deprecated, as it leads to incorrect behaviour " f"when calling {funcname} on a stringified annotation " - f"that references a PEP-695 type parameter. " + f"that references a PEP 695 type parameter. " f"It will be disallowed in Python 3.15." ) warnings.warn(depr_message, category=DeprecationWarning, stacklevel=3)