From b38f68941c42f021172d489b8ee1b89300367ed3 Mon Sep 17 00:00:00 2001 From: sharktide Date: Wed, 5 Mar 2025 12:27:53 -0500 Subject: [PATCH 01/15] Update typing.py --- Lib/typing.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/typing.py b/Lib/typing.py index 4b3c63b25aeeab..014a1e3fd809c3 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -475,6 +475,19 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f t = t.__origin__[args] if is_unpacked: t = Unpack[t] + # Ensure the actual subclass is preserved + if ev_args == t.__args__: + return t + if isinstance(t, GenericAlias): + if _should_unflatten_callable_args(t, ev_args): + return t.__class__(t.__origin__, (ev_args[:-1], ev_args[-1])) + return t.__class__(t.__origin__, ev_args) + if isinstance(t, Union): + return functools.reduce(operator.or_, ev_args) + else: + return t + return t + ev_args = tuple( _eval_type( From bea2a10b4a00dafcf1352c7542484ef491f9ab9e Mon Sep 17 00:00:00 2001 From: sharktide Date: Wed, 5 Mar 2025 14:42:25 -0500 Subject: [PATCH 02/15] gh-130860 finalize pr changes Making pr soon --- Lib/typing.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 014a1e3fd809c3..25e0576839f74f 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -475,19 +475,6 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f t = t.__origin__[args] if is_unpacked: t = Unpack[t] - # Ensure the actual subclass is preserved - if ev_args == t.__args__: - return t - if isinstance(t, GenericAlias): - if _should_unflatten_callable_args(t, ev_args): - return t.__class__(t.__origin__, (ev_args[:-1], ev_args[-1])) - return t.__class__(t.__origin__, ev_args) - if isinstance(t, Union): - return functools.reduce(operator.or_, ev_args) - else: - return t - return t - ev_args = tuple( _eval_type( @@ -499,7 +486,9 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f if ev_args == t.__args__: return t if isinstance(t, GenericAlias): - return GenericAlias(t.__origin__, ev_args) + if _should_unflatten_callable_args(t, ev_args): + return t.__class__(t.__origin__, (ev_args[:-1], ev_args[-1])) + return t.__class__(t.__origin__, ev_args) if isinstance(t, Union): return functools.reduce(operator.or_, ev_args) else: From 3af57b4ac891bf58cd5be22c57eecc443b3c016d Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 21:48:24 +0000 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst diff --git a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst new file mode 100644 index 00000000000000..1ac3f7bad27508 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst @@ -0,0 +1,6 @@ +Type: Bugfix +Title: Fix _eval_type Handling for GenericAlias with Unflattened Arguments and Union Types +Issue: :gh:`130870` + +Detailed changes: +This change improves the handling of `GenericAlias` and `Union` types in `_eval_type`, ensuring that callable arguments for `GenericAlias` are unflattened and that `Union` types are properly evaluated. It resolves complex annotations, including recursive and generic types. All relevant tests, including those for forward references, generics, `Union` types, and recursion, passed successfully without any issues. From d3a633c006a7bdf5aadfdcadd7f374b7509c53b4 Mon Sep 17 00:00:00 2001 From: sharktide Date: Wed, 5 Mar 2025 16:51:45 -0500 Subject: [PATCH 04/15] Update 2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst --- .../Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst index 1ac3f7bad27508..7e298fd8e13f34 100644 --- a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst +++ b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst @@ -1,6 +1,6 @@ Type: Bugfix -Title: Fix _eval_type Handling for GenericAlias with Unflattened Arguments and Union Types +Title: Fix ``_eval_type`` Handling for ``GenericAlias`` with Unflattened Arguments and ``Union`` Types Issue: :gh:`130870` Detailed changes: -This change improves the handling of `GenericAlias` and `Union` types in `_eval_type`, ensuring that callable arguments for `GenericAlias` are unflattened and that `Union` types are properly evaluated. It resolves complex annotations, including recursive and generic types. All relevant tests, including those for forward references, generics, `Union` types, and recursion, passed successfully without any issues. +This change improves the handling of ``GenericAlias`` and ``Union`` types in ``_eval_type``, ensuring that callable arguments for ``GenericAlias`` are unflattened and that ``Union`` types are properly evaluated. It resolves complex annotations, including recursive and generic types. All relevant tests, including those for forward references, generics, ``Union`` types, and recursion, passed successfully without any issues. From b85d920d0f77eb6c56eaba05dbc53c4ed9234731 Mon Sep 17 00:00:00 2001 From: sharktide Date: Thu, 6 Mar 2025 07:42:16 -0500 Subject: [PATCH 05/15] Update 2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst --- .../Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst index 7e298fd8e13f34..5a549cd07f4d96 100644 --- a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst +++ b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst @@ -1,6 +1,4 @@ -Type: Bugfix -Title: Fix ``_eval_type`` Handling for ``GenericAlias`` with Unflattened Arguments and ``Union`` Types Issue: :gh:`130870` Detailed changes: -This change improves the handling of ``GenericAlias`` and ``Union`` types in ``_eval_type``, ensuring that callable arguments for ``GenericAlias`` are unflattened and that ``Union`` types are properly evaluated. It resolves complex annotations, including recursive and generic types. All relevant tests, including those for forward references, generics, ``Union`` types, and recursion, passed successfully without any issues. +This change improves the handling of ``GenericAlias`` and ``Union`` types in ``_eval_type``, ensuring that callable arguments for ``GenericAlias`` are unflattened and that ``Union`` types are properly evaluated. From b0db6a6431104699c02b2909fd1d5019220316b6 Mon Sep 17 00:00:00 2001 From: sharktide Date: Thu, 6 Mar 2025 09:24:54 -0500 Subject: [PATCH 06/15] Update 2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst From 596b6f57503f0956e4436740d46a70a955d9d5ef Mon Sep 17 00:00:00 2001 From: sharktide Date: Thu, 6 Mar 2025 13:43:26 -0500 Subject: [PATCH 07/15] gh-130897 Add test cases --- Lib/test/test_typing.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index e88c811bfcac52..fe06bd6aa12333 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10758,6 +10758,69 @@ def test_eq(self): with self.assertWarns(DeprecationWarning): self.assertNotEqual(int, typing._UnionGenericAlias) +class MyType: + pass + +class test_generic_alias_handling(BaseTestCase): + def test_forward_ref(self): + fwd_ref = ForwardRef('MyType') + result = _eval_type(fwd_ref, globals(), locals()) + self.assertIs(result, MyType, f"Expected MyType, got {result}") + + def test_generic_alias(self): + fwd_ref = ForwardRef('MyType') + generic_list = List[fwd_ref] + result = _eval_type(generic_list, globals(), locals()) + self.assertEqual(result, List[MyType], f"Expected List[MyType], got {result}") + + def test_union(self): + fwd_ref_1 = ForwardRef('MyType') + fwd_ref_2 = ForwardRef('int') + union_type = Union[fwd_ref_1, fwd_ref_2] + + result = _eval_type(union_type, globals(), locals()) + self.assertEqual(result, Union[MyType, int], f"Expected Union[MyType, int], got {result}") + + def test_recursive_forward_ref(self): + recursive_ref = ForwardRef('RecursiveType') + globals()['RecursiveType'] = recursive_ref + + recursive_type = Dict[str, List[recursive_ref]] + + result = _eval_type(recursive_type, globals(), locals(), recursive_guard={recursive_ref}) + + self.assertEqual(result, Dict[str, List[recursive_ref]], f"Expected Dict[str, List[RecursiveType]], got {result}") + + def test_callable_unpacking(self): + fwd_ref = ForwardRef('MyType') + callable_type = Callable[[fwd_ref, int], str] + result = _eval_type(callable_type, globals(), locals()) + + self.assertEqual(result, Callable[[MyType, int], str], f"Expected Callable[[MyType, int], str], got {result}") + + def test_unpacked_generic(self): + fwd_ref = ForwardRef('MyType') + generic_type = Tuple[fwd_ref, int] + + result = _eval_type(generic_type, globals(), locals()) + self.assertEqual(result, Tuple[MyType, int], f"Expected Tuple[MyType, int], got {result}") + + def test_preservation_of_type(self): + fwd_ref_1 = ForwardRef('MyType') + fwd_ref_2 = ForwardRef('int') + complex_type = Dict[str, Union[fwd_ref_1, fwd_ref_2]] + + result = _eval_type(complex_type, globals(), locals()) + self.assertEqual(result, Dict[str, Union[MyType, int]], f"Expected Dict[str, Union[MyType, int]], got {result}") + + def test_callable_unflattening(self): + callable_type = Callable[[int, str], bool] + result = _eval_type(callable_type, globals(), locals(), type_params=()) + self.assertEqual(result, Callable[[int, str], bool], f"Expected Callable[[int, str], bool], got {result}") + + callable_type_packed = Callable[[int, str], bool] # Correct format for callable + result = _eval_type(callable_type_packed, globals(), locals(), type_params=()) + self.assertEqual(result, Callable[[int, str], bool], f"Expected Callable[[int, str], bool], got {result}") def load_tests(loader, tests, pattern): import doctest From 37b760c2e96ba8f5c652713f67b8ddaa4e63dfec Mon Sep 17 00:00:00 2001 From: sharktide Date: Thu, 6 Mar 2025 13:56:59 -0500 Subject: [PATCH 08/15] Update test_typing.py --- Lib/test/test_typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index fe06bd6aa12333..b0c09510eedf3b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -41,6 +41,7 @@ from typing import TypeAlias from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs from typing import TypeGuard, TypeIs, NoDefault +from typing import _eval_type import abc import textwrap import typing @@ -10761,7 +10762,7 @@ def test_eq(self): class MyType: pass -class test_generic_alias_handling(BaseTestCase): +class TestGenericAliasHandling(BaseTestCase): def test_forward_ref(self): fwd_ref = ForwardRef('MyType') result = _eval_type(fwd_ref, globals(), locals()) From aeb602b1742abb50d3a46c149f819e42ebbcfd80 Mon Sep 17 00:00:00 2001 From: sharktide Date: Thu, 6 Mar 2025 14:00:11 -0500 Subject: [PATCH 09/15] Remove whitespace test_typing.py --- Lib/test/test_typing.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b0c09510eedf3b..bf029fc8413f7e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10778,31 +10778,25 @@ def test_union(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') union_type = Union[fwd_ref_1, fwd_ref_2] - result = _eval_type(union_type, globals(), locals()) self.assertEqual(result, Union[MyType, int], f"Expected Union[MyType, int], got {result}") def test_recursive_forward_ref(self): recursive_ref = ForwardRef('RecursiveType') globals()['RecursiveType'] = recursive_ref - recursive_type = Dict[str, List[recursive_ref]] - result = _eval_type(recursive_type, globals(), locals(), recursive_guard={recursive_ref}) - self.assertEqual(result, Dict[str, List[recursive_ref]], f"Expected Dict[str, List[RecursiveType]], got {result}") def test_callable_unpacking(self): fwd_ref = ForwardRef('MyType') callable_type = Callable[[fwd_ref, int], str] result = _eval_type(callable_type, globals(), locals()) - self.assertEqual(result, Callable[[MyType, int], str], f"Expected Callable[[MyType, int], str], got {result}") def test_unpacked_generic(self): fwd_ref = ForwardRef('MyType') generic_type = Tuple[fwd_ref, int] - result = _eval_type(generic_type, globals(), locals()) self.assertEqual(result, Tuple[MyType, int], f"Expected Tuple[MyType, int], got {result}") @@ -10810,7 +10804,6 @@ def test_preservation_of_type(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') complex_type = Dict[str, Union[fwd_ref_1, fwd_ref_2]] - result = _eval_type(complex_type, globals(), locals()) self.assertEqual(result, Dict[str, Union[MyType, int]], f"Expected Dict[str, Union[MyType, int]], got {result}") From b31ae23c3db084eafb5aba1f97ff1117eb6c1d24 Mon Sep 17 00:00:00 2001 From: Rihaan Meher Date: Mon, 5 May 2025 07:45:43 -0400 Subject: [PATCH 10/15] Add improved tests for #130897 --- Lib/test/test_typing.py | 81 ++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 39aacac6cc0b9a..7b76d841be1b7b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10732,62 +10732,107 @@ def test_eq(self): with self.assertWarns(DeprecationWarning): self.assertNotEqual(int, typing._UnionGenericAlias) +# Define MyType class MyType: pass class TestGenericAliasHandling(BaseTestCase): + def test_forward_ref(self): fwd_ref = ForwardRef('MyType') - result = _eval_type(fwd_ref, globals(), locals()) - self.assertIs(result, MyType, f"Expected MyType, got {result}") + + def func(arg: fwd_ref): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], MyType, f"Expected MyType, got {result['arg']}") def test_generic_alias(self): fwd_ref = ForwardRef('MyType') generic_list = List[fwd_ref] - result = _eval_type(generic_list, globals(), locals()) - self.assertEqual(result, List[MyType], f"Expected List[MyType], got {result}") + + def func(arg: generic_list): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], List[MyType], f"Expected List[MyType], got {result['arg']}") def test_union(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') union_type = Union[fwd_ref_1, fwd_ref_2] - result = _eval_type(union_type, globals(), locals()) - self.assertEqual(result, Union[MyType, int], f"Expected Union[MyType, int], got {result}") + + def func(arg: union_type): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], Union[MyType, int], f"Expected Union[MyType, int], got {result['arg']}") def test_recursive_forward_ref(self): recursive_ref = ForwardRef('RecursiveType') globals()['RecursiveType'] = recursive_ref recursive_type = Dict[str, List[recursive_ref]] - result = _eval_type(recursive_type, globals(), locals(), recursive_guard={recursive_ref}) - self.assertEqual(result, Dict[str, List[recursive_ref]], f"Expected Dict[str, List[RecursiveType]], got {result}") + + def func(arg: recursive_type): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], Dict[str, List[recursive_ref]], f"Expected Dict[str, List[RecursiveType]], got {result['arg']}") def test_callable_unpacking(self): fwd_ref = ForwardRef('MyType') callable_type = Callable[[fwd_ref, int], str] - result = _eval_type(callable_type, globals(), locals()) - self.assertEqual(result, Callable[[MyType, int], str], f"Expected Callable[[MyType, int], str], got {result}") + + def func(arg1: fwd_ref, arg2: int) -> str: + return "test" + + result = get_type_hints(func) + self.assertEqual(result['arg1'], MyType, f"Expected MyType for arg1, got {result['arg1']}") + self.assertEqual(result['arg2'], int, f"Expected int for arg2, got {result['arg2']}") + self.assertEqual(result['return'], str, f"Expected str for return, got {result['return']}") def test_unpacked_generic(self): fwd_ref = ForwardRef('MyType') generic_type = Tuple[fwd_ref, int] - result = _eval_type(generic_type, globals(), locals()) - self.assertEqual(result, Tuple[MyType, int], f"Expected Tuple[MyType, int], got {result}") + + def func(arg: generic_type): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], Tuple[MyType, int], f"Expected Tuple[MyType, int], got {result['arg']}") def test_preservation_of_type(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') complex_type = Dict[str, Union[fwd_ref_1, fwd_ref_2]] - result = _eval_type(complex_type, globals(), locals()) - self.assertEqual(result, Dict[str, Union[MyType, int]], f"Expected Dict[str, Union[MyType, int]], got {result}") + + def func(arg: complex_type): + pass + + result = get_type_hints(func) + self.assertEqual(result['arg'], Dict[str, Union[MyType, int]], f"Expected Dict[str, Union[MyType, int]], got {result['arg']}") def test_callable_unflattening(self): callable_type = Callable[[int, str], bool] - result = _eval_type(callable_type, globals(), locals(), type_params=()) - self.assertEqual(result, Callable[[int, str], bool], f"Expected Callable[[int, str], bool], got {result}") + + def func(arg1: int, arg2: str) -> bool: + return True + + result = get_type_hints(func) + self.assertEqual(result['arg1'], int, f"Expected int for arg1, got {result['arg1']}") + self.assertEqual(result['arg2'], str, f"Expected str for arg2, got {result['arg2']}") + self.assertEqual(result['return'], bool, f"Expected bool for return, got {result['return']}") callable_type_packed = Callable[[int, str], bool] # Correct format for callable - result = _eval_type(callable_type_packed, globals(), locals(), type_params=()) - self.assertEqual(result, Callable[[int, str], bool], f"Expected Callable[[int, str], bool], got {result}") + + def func_packed(arg1: int, arg2: str) -> bool: + return True + + result = get_type_hints(func_packed) + self.assertEqual(result['arg1'], int, f"Expected int for arg1, got {result['arg1']}") + self.assertEqual(result['arg2'], str, f"Expected str for arg2, got {result['arg2']}") + self.assertEqual(result['return'], bool, f"Expected bool for return, got {result['return']}") + def load_tests(loader, tests, pattern): import doctest From 221cc920fc493c60fc6e4d566500914a54380cd1 Mon Sep 17 00:00:00 2001 From: Rihaan Meher Date: Mon, 5 May 2025 08:08:14 -0400 Subject: [PATCH 11/15] Update test_typing.py --- Lib/test/test_typing.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7b76d841be1b7b..6e03dcc54807fd 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10740,20 +10740,20 @@ class TestGenericAliasHandling(BaseTestCase): def test_forward_ref(self): fwd_ref = ForwardRef('MyType') - + def func(arg: fwd_ref): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], MyType, f"Expected MyType, got {result['arg']}") def test_generic_alias(self): fwd_ref = ForwardRef('MyType') generic_list = List[fwd_ref] - + def func(arg: generic_list): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], List[MyType], f"Expected List[MyType], got {result['arg']}") @@ -10761,10 +10761,10 @@ def test_union(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') union_type = Union[fwd_ref_1, fwd_ref_2] - + def func(arg: union_type): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], Union[MyType, int], f"Expected Union[MyType, int], got {result['arg']}") @@ -10772,20 +10772,20 @@ def test_recursive_forward_ref(self): recursive_ref = ForwardRef('RecursiveType') globals()['RecursiveType'] = recursive_ref recursive_type = Dict[str, List[recursive_ref]] - + def func(arg: recursive_type): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], Dict[str, List[recursive_ref]], f"Expected Dict[str, List[RecursiveType]], got {result['arg']}") def test_callable_unpacking(self): fwd_ref = ForwardRef('MyType') callable_type = Callable[[fwd_ref, int], str] - + def func(arg1: fwd_ref, arg2: int) -> str: return "test" - + result = get_type_hints(func) self.assertEqual(result['arg1'], MyType, f"Expected MyType for arg1, got {result['arg1']}") self.assertEqual(result['arg2'], int, f"Expected int for arg2, got {result['arg2']}") @@ -10794,10 +10794,10 @@ def func(arg1: fwd_ref, arg2: int) -> str: def test_unpacked_generic(self): fwd_ref = ForwardRef('MyType') generic_type = Tuple[fwd_ref, int] - + def func(arg: generic_type): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], Tuple[MyType, int], f"Expected Tuple[MyType, int], got {result['arg']}") @@ -10805,29 +10805,29 @@ def test_preservation_of_type(self): fwd_ref_1 = ForwardRef('MyType') fwd_ref_2 = ForwardRef('int') complex_type = Dict[str, Union[fwd_ref_1, fwd_ref_2]] - + def func(arg: complex_type): pass - + result = get_type_hints(func) self.assertEqual(result['arg'], Dict[str, Union[MyType, int]], f"Expected Dict[str, Union[MyType, int]], got {result['arg']}") def test_callable_unflattening(self): callable_type = Callable[[int, str], bool] - + def func(arg1: int, arg2: str) -> bool: return True - + result = get_type_hints(func) self.assertEqual(result['arg1'], int, f"Expected int for arg1, got {result['arg1']}") self.assertEqual(result['arg2'], str, f"Expected str for arg2, got {result['arg2']}") self.assertEqual(result['return'], bool, f"Expected bool for return, got {result['return']}") - callable_type_packed = Callable[[int, str], bool] # Correct format for callable - + callable_type_packed = Callable[[int, str], bool] + def func_packed(arg1: int, arg2: str) -> bool: return True - + result = get_type_hints(func_packed) self.assertEqual(result['arg1'], int, f"Expected int for arg1, got {result['arg1']}") self.assertEqual(result['arg2'], str, f"Expected str for arg2, got {result['arg2']}") From 6950f759034e821319df37785213e60a0566ffd4 Mon Sep 17 00:00:00 2001 From: sharktide Date: Mon, 12 May 2025 12:23:01 -0400 Subject: [PATCH 12/15] Add new test to test_typing.py to demonstrate fixed behaviour in pythongh-130870 --- Lib/test/test_typing.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1e39752bf1518f..7e1accb3748adc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10772,6 +10772,20 @@ def func_packed(arg1: int, arg2: str) -> bool: def test_hashable(self): self.assertEqual(hash(typing._UnionGenericAlias), hash(Union)) +class TestCallableAlias(BaseTestCase): + def test_callable_alias_preserves_subclass(self): + C = Callable[[str, ForwardRef("int")], int] + class A: + c: C + + hints = get_type_hints(A) + + # Ensure evaluated type retains the correct class + self.assertEqual(hints['c'].__class__, C.__class__) + + # Instead of comparing raw ForwardRef, check if the resolution is correct + expected_args = tuple(int if isinstance(arg, ForwardRef) else arg for arg in C.__args__) + self.assertEqual(hints['c'].__args__, expected_args) def load_tests(loader, tests, pattern): import doctest From ae5e0e81825f73d0e4be75789e23a8a5fec7b26c Mon Sep 17 00:00:00 2001 From: sharktide Date: Mon, 12 May 2025 12:29:55 -0400 Subject: [PATCH 13/15] Make linter happy --- 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 7e1accb3748adc..305a4c017af8fc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10777,7 +10777,7 @@ def test_callable_alias_preserves_subclass(self): C = Callable[[str, ForwardRef("int")], int] class A: c: C - + hints = get_type_hints(A) # Ensure evaluated type retains the correct class From 6278b37749aac85a62462013910f55439cb21c32 Mon Sep 17 00:00:00 2001 From: sharktide Date: Mon, 12 May 2025 16:33:19 -0400 Subject: [PATCH 14/15] Update test import statements and put in the actually correct test. (Ignore previous commit) --- Lib/test/test_typing.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 305a4c017af8fc..d763f09f060655 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3,6 +3,7 @@ import collections import collections.abc from collections import defaultdict +from collections.abc import Callable as ABCallable from functools import lru_cache, wraps, reduce import gc import inspect @@ -10774,15 +10775,18 @@ def test_hashable(self): class TestCallableAlias(BaseTestCase): def test_callable_alias_preserves_subclass(self): - C = Callable[[str, ForwardRef("int")], int] + C = ABCallable[[str, ForwardRef('int')], int] class A: c: C + # Explicitly pass global namespace to ensure correct resolution + hints = get_type_hints(A, globalns=globals()) - hints = get_type_hints(A) - - # Ensure evaluated type retains the correct class + # Ensure evaluated type retains the correct subclass (_CallableGenericAlias) self.assertEqual(hints['c'].__class__, C.__class__) + # Ensure evaluated type retains correct origin + self.assertEqual(hints['c'].__origin__, C.__origin__) + # Instead of comparing raw ForwardRef, check if the resolution is correct expected_args = tuple(int if isinstance(arg, ForwardRef) else arg for arg in C.__args__) self.assertEqual(hints['c'].__args__, expected_args) From f676ac0124c5584579f20b28eddcced32166e2fd Mon Sep 17 00:00:00 2001 From: Rihaan Meher Date: Mon, 12 May 2025 16:40:45 -0400 Subject: [PATCH 15/15] Update 2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst --- .../Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst index 5a549cd07f4d96..e52af134eeff63 100644 --- a/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst +++ b/Misc/NEWS.d/next/Library/2025-03-05-21-48-22.gh-issue-130870.uDz6AQ.rst @@ -1,4 +1 @@ -Issue: :gh:`130870` - -Detailed changes: -This change improves the handling of ``GenericAlias`` and ``Union`` types in ``_eval_type``, ensuring that callable arguments for ``GenericAlias`` are unflattened and that ``Union`` types are properly evaluated. +Ensure that typing.Callable retains its subclass (_CallableGenericAlias) instead of being incorrectly converted to GenericAlias.