summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Sobolev <mail@sobolevn.me>2023-03-15 09:33:41 (GMT)
committerGitHub <noreply@github.com>2023-03-15 09:33:41 (GMT)
commit2b5781d659ce3ffe03d4c1f46e4875e604cf2d88 (patch)
treedda052375c1a44ffdc0dd5b610163a539c0d2ec3
parent8647ba4b639077e201751ae6dbd82e8bfcf80895 (diff)
downloadcpython-2b5781d659ce3ffe03d4c1f46e4875e604cf2d88.zip
cpython-2b5781d659ce3ffe03d4c1f46e4875e604cf2d88.tar.gz
cpython-2b5781d659ce3ffe03d4c1f46e4875e604cf2d88.tar.bz2
gh-102615: Use `list` instead of `tuple` in `repr` of paramspec (#102637)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
-rw-r--r--Lib/test/test_typing.py45
-rw-r--r--Lib/typing.py7
-rw-r--r--Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst3
3 files changed, 52 insertions, 3 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index c17be6c..89c725c 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -3809,6 +3809,51 @@ class GenericTests(BaseTestCase):
self.assertEqual(Y.__qualname__,
'GenericTests.test_repr_2.<locals>.Y')
+ 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[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)}$",
+ )
+
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 8d40e92..ab33439 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -230,16 +230,17 @@ 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__
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 0000000..3330683
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-03-13-12-05-55.gh-issue-102615.NcA_ZL.rst
@@ -0,0 +1,3 @@
+Typing: Improve the ``repr`` of generic aliases for classes generic over a
+:class:`~typing.ParamSpec`. (Use square brackets to represent a parameter
+list.)