From 8e4620cf1e596e714f54579f8ee6721d1cc6982f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 13 Mar 2023 12:06:29 +0300 Subject: [PATCH 1/5] gh-102615: Use `list` instead of `tuple` in `repr` of paramspec --- Lib/test/test_typing.py | 20 +++++++++++++++++++ Lib/typing.py | 5 ++++- ...-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c17be6cd0bbc4a..bd48b524317d5d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3809,6 +3809,26 @@ class Y(C[int]): self.assertEqual(Y.__qualname__, 'GenericTests.test_repr_2..Y') + def test_repr_3(self): + T = TypeVar('T') + P = ParamSpec('P') + + class MyCallable(Generic[P, T]): + pass + + self.assertEqual( + repr(MyCallable[[], bool]).split('.')[-1], + 'MyCallable[[], bool]', + ) + self.assertEqual( + repr(MyCallable[[int], bool]).split('.')[-1], + 'MyCallable[[int], bool]', + ) + self.assertEqual( + repr(MyCallable[[int, str], bool]).split('.')[-1], + "MyCallable[[int, str], bool]", + ) + def test_eq_1(self): self.assertEqual(Generic, Generic) self.assertEqual(Generic[T], Generic[T]) diff --git a/Lib/typing.py b/Lib/typing.py index 8d40e923bb1d08..d8ca1529105349 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -237,9 +237,12 @@ def _type_repr(obj): return obj.__qualname__ return f'{obj.__module__}.{obj.__qualname__}' if obj is ...: - return('...') + return '...' if isinstance(obj, types.FunctionType): return obj.__name__ + if isinstance(obj, tuple): + # Special case for `repr` of types with `ParamSpec`: + return '[' + ', '.join(_type_repr(t) for t in obj) + ']' return repr(obj) diff --git a/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst b/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst new file mode 100644 index 00000000000000..0d9ea66e2792f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst @@ -0,0 +1 @@ +Use list instead of tuple in ``repr`` of generic aliases with ``ParamSpec``. From 4e12bd6f5576e9448906560fffb8741f7758fa0a Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 14 Mar 2023 18:10:18 +0300 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 21 ++++++++----------- ...-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst | 4 +++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index bd48b524317d5d..fe697c2b1a4a37 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3816,18 +3816,15 @@ def test_repr_3(self): class MyCallable(Generic[P, T]): pass - self.assertEqual( - repr(MyCallable[[], bool]).split('.')[-1], - 'MyCallable[[], bool]', - ) - self.assertEqual( - repr(MyCallable[[int], bool]).split('.')[-1], - 'MyCallable[[int], bool]', - ) - self.assertEqual( - repr(MyCallable[[int, str], bool]).split('.')[-1], - "MyCallable[[int, str], bool]", - ) + object_to_expected_repr = { + MyCallable[[], bool]: "MyCallable[[], bool]", + MyCallable[[int], bool]: "MyCallable[[int], bool]", + MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]" + } + + for obj, expected_repr in object_to_expected_repr.items(): + with self.subTest(obj=obj, expected_repr=expected_repr): + self.assertEqual(repr(obj), MyCallable.__module__ + expected_repr) def test_eq_1(self): self.assertEqual(Generic, Generic) diff --git a/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst b/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst index 0d9ea66e2792f1..333068369bc4f7 100644 --- a/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst +++ b/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst @@ -1 +1,3 @@ -Use list instead of tuple in ``repr`` of generic aliases with ``ParamSpec``. +Typing: Improve the ``repr`` of generic aliases for classes generic over a +:class:`~typing.ParamSpec`. (Use square brackets to represent a parameter +list.) From d226833e067c0f25daede3d7f580a6e2329d0c52 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 14 Mar 2023 18:17:45 +0300 Subject: [PATCH 3/5] Update Lib/test/test_typing.py Co-authored-by: Alex Waygood --- 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 fe697c2b1a4a37..981423012db328 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3824,7 +3824,7 @@ class MyCallable(Generic[P, T]): for obj, expected_repr in object_to_expected_repr.items(): with self.subTest(obj=obj, expected_repr=expected_repr): - self.assertEqual(repr(obj), MyCallable.__module__ + expected_repr) + self.assertEqual(repr(obj), f"{MyCallable.__module__}.{expected_repr}") def test_eq_1(self): self.assertEqual(Generic, Generic) From dc36254b79fbd9945e3a75c45e3388c5686dfc92 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 14 Mar 2023 18:33:11 +0300 Subject: [PATCH 4/5] Update Lib/test/test_typing.py Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 981423012db328..e5eb53f1c3400e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3824,7 +3824,10 @@ class MyCallable(Generic[P, T]): for obj, expected_repr in object_to_expected_repr.items(): with self.subTest(obj=obj, expected_repr=expected_repr): - self.assertEqual(repr(obj), f"{MyCallable.__module__}.{expected_repr}") + self.assertRegex( + repr(obj), + fr"^{re.escape(MyCallable.__module__)}.*\.{re.escape(expected_repr)}$" + ) def test_eq_1(self): self.assertEqual(Generic, Generic) From 3451d4d6de7034e63d792eac8983ceeb91c41157 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 15 Mar 2023 12:03:43 +0300 Subject: [PATCH 5/5] Address review --- Lib/test/test_typing.py | 33 +++++++++++++++++++++++++++++---- Lib/typing.py | 2 -- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index e5eb53f1c3400e..89c725cda54f12 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3811,22 +3811,47 @@ class Y(C[int]): def test_repr_3(self): T = TypeVar('T') + T1 = TypeVar('T1') P = ParamSpec('P') + P2 = ParamSpec('P2') + Ts = TypeVarTuple('Ts') class MyCallable(Generic[P, T]): pass + class DoubleSpec(Generic[P, P2, T]): + pass + + class TsP(Generic[*Ts, P]): + pass + object_to_expected_repr = { - MyCallable[[], bool]: "MyCallable[[], bool]", - MyCallable[[int], bool]: "MyCallable[[int], bool]", - MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]" + MyCallable[P, T]: "MyCallable[~P, ~T]", + MyCallable[Concatenate[T1, P], T]: "MyCallable[typing.Concatenate[~T1, ~P], ~T]", + MyCallable[[], bool]: "MyCallable[[], bool]", + MyCallable[[int], bool]: "MyCallable[[int], bool]", + MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]", + MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]", + MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[*Ts, ~P], ~T]", + + DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]", + DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]", + DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]", + + TsP[*Ts, P]: "TsP[*Ts, ~P]", + TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]", + TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]", + + # These lines are just too long to fit: + MyCallable[Concatenate[*Ts, P], int][int, str, [bool, float]]: + "MyCallable[[int, str, bool, float], int]", } for obj, expected_repr in object_to_expected_repr.items(): with self.subTest(obj=obj, expected_repr=expected_repr): self.assertRegex( repr(obj), - fr"^{re.escape(MyCallable.__module__)}.*\.{re.escape(expected_repr)}$" + fr"^{re.escape(MyCallable.__module__)}.*\.{re.escape(expected_repr)}$", ) def test_eq_1(self): diff --git a/Lib/typing.py b/Lib/typing.py index d8ca1529105349..ab334395676159 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -230,8 +230,6 @@ def _type_repr(obj): typically enough to uniquely identify a type. For everything else, we fall back on repr(obj). """ - if isinstance(obj, types.GenericAlias): - return repr(obj) if isinstance(obj, type): if obj.__module__ == 'builtins': return obj.__qualname__