From 205b4bf6c93be7205d0cebbd8e9866fd16feedf7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 10 Oct 2016 02:52:23 +0200 Subject: [PATCH 1/8] Alternative repr for generics --- python2/typing.py | 21 +++++++++++---------- src/typing.py | 21 +++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index cb5b3edd..e5e5d724 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1115,17 +1115,18 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - if self.__origin__ is not None: - r = repr(self.__origin__) - else: - r = super(GenericMeta, self).__repr__() - if self.__args__: - r += '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__args__)) - if self.__parameters__: - r += '<%s>' % ( + return super(GenericMeta, self).__repr__() + self._argrepr() + + def _argrepr(self): + if self.__origin__ is None: + return '[%s]' % ( ', '.join(_type_repr(p) for p in self.__parameters__)) - return r + r = self.__origin__._argrepr() + for i, arg in enumerate(self.__args__): + r = stdlib_re.sub(stdlib_re.escape( + _type_repr(self.__origin__.__parameters__[i])) + + '(?=[,\]])', '{%r}' % i, r) + return r.format(*(_type_repr(arg) for arg in self.__args__)) def __eq__(self, other): if not isinstance(other, GenericMeta): diff --git a/src/typing.py b/src/typing.py index 35d562e0..5b874615 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1006,17 +1006,18 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - if self.__origin__ is not None: - r = repr(self.__origin__) - else: - r = super().__repr__() - if self.__args__: - r += '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__args__)) - if self.__parameters__: - r += '<%s>' % ( + return super().__repr__() + self._argrepr() + + def _argrepr(self): + if self.__origin__ is None: + return '[%s]' % ( ', '.join(_type_repr(p) for p in self.__parameters__)) - return r + r = self.__origin__._argrepr() + for i, arg in enumerate(self.__args__): + r = stdlib_re.sub(stdlib_re.escape( + _type_repr(self.__origin__.__parameters__[i])) + + '(?=[,\]])', '{%r}' % i, r) + return r.format(*(_type_repr(arg) for arg in self.__args__)) def __eq__(self, other): if not isinstance(other, GenericMeta): From b42f304d203a0abcf12fa0f6ac8d343ff9a48340 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 10 Oct 2016 10:56:57 +0200 Subject: [PATCH 2/8] Small fix and tests --- python2/test_typing.py | 31 ++++++++++++++++++++++++++----- python2/typing.py | 6 ++++-- src/test_typing.py | 31 ++++++++++++++++++++++++++----- src/typing.py | 6 ++++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 8dd1acb0..41e4cd6a 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -521,9 +521,9 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping<~XK, ~XV>') + __name__ + '.' + 'SimpleMapping[~XK, ~XV]') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping<~XK, ~XV>') + __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') def test_chain_repr(self): T = TypeVar('T') @@ -547,7 +547,28 @@ class C(Generic[T]): self.assertNotEqual(Z, Y[T]) self.assertTrue(str(Z).endswith( - '.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]')) + '.C[typing.Tuple[str, int]]')) + + def test_new_repr(self): + T = TypeVar('T') + U = TypeVar('U', covariant=True) + S = TypeVar('S') + + self.assertEqual(repr(List), 'typing.List[~T]') + self.assertEqual(repr(List[T]), 'typing.List[~T]') + self.assertEqual(repr(List[U]), 'typing.List[+U]') + self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') + self.assertEqual(repr(List[int]), 'typing.List[int]') + + def test_new_repr_complex(self): + T = TypeVar('T') + TS = TypeVar('TS') + + self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]') + self.assertEqual(repr(List[Tuple[T, TS]][int, T]), + 'typing.List[typing.Tuple[int, ~T]]') + self.assertEqual(repr(List[Tuple[T, T]][List[int]]), + 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]') def test_dict(self): T = TypeVar('T') @@ -635,12 +656,12 @@ class C(Generic[T]): if not PY32: self.assertEqual(C.__qualname__, 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C<~T>') + self.assertEqual(repr(C).split('.')[-1], 'C[~T]') X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: self.assertEqual(X.__qualname__, 'C') - self.assertEqual(repr(X).split('.')[-1], 'C<~T>[int]') + self.assertEqual(repr(X).split('.')[-1], 'C[int]') class Y(C[int]): pass diff --git a/python2/typing.py b/python2/typing.py index e5e5d724..20ee85ff 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1119,8 +1119,10 @@ def __repr__(self): def _argrepr(self): if self.__origin__ is None: - return '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__parameters__)) + if self.__parameters__: + return '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__parameters__)) + return '' r = self.__origin__._argrepr() for i, arg in enumerate(self.__args__): r = stdlib_re.sub(stdlib_re.escape( diff --git a/src/test_typing.py b/src/test_typing.py index dff737ae..eb9aeead 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -548,9 +548,9 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping<~XK, ~XV>') + __name__ + '.' + 'SimpleMapping[~XK, ~XV]') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping<~XK, ~XV>') + __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') def test_chain_repr(self): T = TypeVar('T') @@ -574,7 +574,28 @@ class C(Generic[T]): self.assertNotEqual(Z, Y[T]) self.assertTrue(str(Z).endswith( - '.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]')) + '.C[typing.Tuple[str, int]]')) + + def test_new_repr(self): + T = TypeVar('T') + U = TypeVar('U', covariant=True) + S = TypeVar('S') + + self.assertEqual(repr(List), 'typing.List[~T]') + self.assertEqual(repr(List[T]), 'typing.List[~T]') + self.assertEqual(repr(List[U]), 'typing.List[+U]') + self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') + self.assertEqual(repr(List[int]), 'typing.List[int]') + + def test_new_repr_complex(self): + T = TypeVar('T') + TS = TypeVar('TS') + + self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]') + self.assertEqual(repr(List[Tuple[T, TS]][int, T]), + 'typing.List[typing.Tuple[int, ~T]]') + self.assertEqual(repr(List[Tuple[T, T]][List[int]]), + 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]') def test_dict(self): T = TypeVar('T') @@ -662,12 +683,12 @@ class C(Generic[T]): if not PY32: self.assertEqual(C.__qualname__, 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C<~T>') + self.assertEqual(repr(C).split('.')[-1], 'C[~T]') X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: self.assertEqual(X.__qualname__, 'C') - self.assertEqual(repr(X).split('.')[-1], 'C<~T>[int]') + self.assertEqual(repr(X).split('.')[-1], 'C[int]') class Y(C[int]): pass diff --git a/src/typing.py b/src/typing.py index 5b874615..6061a94e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1010,8 +1010,10 @@ def __repr__(self): def _argrepr(self): if self.__origin__ is None: - return '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__parameters__)) + if self.__parameters__: + return '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__parameters__)) + return '' r = self.__origin__._argrepr() for i, arg in enumerate(self.__args__): r = stdlib_re.sub(stdlib_re.escape( From e0caaff7590ed332f1201d7b9295320ff4adb37a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 10 Oct 2016 12:04:21 +0200 Subject: [PATCH 3/8] Simply code --- python2/typing.py | 2 +- src/typing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 20ee85ff..aecf3a33 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1128,7 +1128,7 @@ def _argrepr(self): r = stdlib_re.sub(stdlib_re.escape( _type_repr(self.__origin__.__parameters__[i])) + '(?=[,\]])', '{%r}' % i, r) - return r.format(*(_type_repr(arg) for arg in self.__args__)) + return r.format(*map(_type_repr, self.__args__)) def __eq__(self, other): if not isinstance(other, GenericMeta): diff --git a/src/typing.py b/src/typing.py index 6061a94e..fdb664b2 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1019,7 +1019,7 @@ def _argrepr(self): r = stdlib_re.sub(stdlib_re.escape( _type_repr(self.__origin__.__parameters__[i])) + '(?=[,\]])', '{%r}' % i, r) - return r.format(*(_type_repr(arg) for arg in self.__args__)) + return r.format(*map(_type_repr, self.__args__)) def __eq__(self, other): if not isinstance(other, GenericMeta): From 9d035e35a923d6a86e952c2f5d019ee0a5404e55 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 10 Oct 2016 12:15:17 +0200 Subject: [PATCH 4/8] Code formatting --- python2/typing.py | 14 ++++++-------- src/typing.py | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index aecf3a33..b68f81d4 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1119,15 +1119,13 @@ def __repr__(self): def _argrepr(self): if self.__origin__ is None: - if self.__parameters__: - return '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__parameters__)) - return '' + if not self.__parameters__: + return '' + return '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) r = self.__origin__._argrepr() - for i, arg in enumerate(self.__args__): - r = stdlib_re.sub(stdlib_re.escape( - _type_repr(self.__origin__.__parameters__[i])) - + '(?=[,\]])', '{%r}' % i, r) + for i in range(len(self.__args__)): # replace free parameters with args + par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) + r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) return r.format(*map(_type_repr, self.__args__)) def __eq__(self, other): diff --git a/src/typing.py b/src/typing.py index fdb664b2..633d507b 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1010,15 +1010,13 @@ def __repr__(self): def _argrepr(self): if self.__origin__ is None: - if self.__parameters__: - return '[%s]' % ( - ', '.join(_type_repr(p) for p in self.__parameters__)) - return '' + if not self.__parameters__: + return '' + return '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) r = self.__origin__._argrepr() - for i, arg in enumerate(self.__args__): - r = stdlib_re.sub(stdlib_re.escape( - _type_repr(self.__origin__.__parameters__[i])) - + '(?=[,\]])', '{%r}' % i, r) + for i in range(len(self.__args__)): # replace free parameters with args + par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) + r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) return r.format(*map(_type_repr, self.__args__)) def __eq__(self, other): From 47bf98f0642de6de8c6d6d5e4db8bff952dea0f9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 11 Oct 2016 10:11:42 +0200 Subject: [PATCH 5/8] Reorganize code to take care about Generic[T] and _Protocol[T] --- python2/test_typing.py | 8 ++++++++ python2/typing.py | 11 +++++++---- src/test_typing.py | 8 ++++++++ src/typing.py | 11 +++++++---- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 41e4cd6a..bb5a3425 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -570,6 +570,14 @@ def test_new_repr_complex(self): self.assertEqual(repr(List[Tuple[T, T]][List[int]]), 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]') + def test_new_repr_bare(self): + T = TypeVar('T') + self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]') + self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]') + class C(typing.Dict[Any, Any]): pass + # this line should just work + repr(C.__mro__) + def test_dict(self): T = TypeVar('T') diff --git a/python2/typing.py b/python2/typing.py index b68f81d4..f5395a89 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1115,14 +1115,17 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - return super(GenericMeta, self).__repr__() + self._argrepr() + return super(GenericMeta, self).__repr__() + self._arg_repr() - def _argrepr(self): + def _arg_repr(self): + par_repr = '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) + if self.__origin__ in [Generic, _Protocol]: + return par_repr if self.__origin__ is None: if not self.__parameters__: return '' - return '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) - r = self.__origin__._argrepr() + return par_repr + r = self.__origin__._arg_repr() for i in range(len(self.__args__)): # replace free parameters with args par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) diff --git a/src/test_typing.py b/src/test_typing.py index eb9aeead..15af5fb5 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -597,6 +597,14 @@ def test_new_repr_complex(self): self.assertEqual(repr(List[Tuple[T, T]][List[int]]), 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]') + def test_new_repr_bare(self): + T = TypeVar('T') + self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]') + self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]') + class C(typing.Dict[Any, Any]): ... + # this line should just work + repr(C.__mro__) + def test_dict(self): T = TypeVar('T') diff --git a/src/typing.py b/src/typing.py index 633d507b..264a2ef3 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1006,14 +1006,17 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - return super().__repr__() + self._argrepr() + return super().__repr__() + self._arg_repr() - def _argrepr(self): + def _arg_repr(self): + par_repr = '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) + if self.__origin__ in [Generic, _Protocol]: + return par_repr if self.__origin__ is None: if not self.__parameters__: return '' - return '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) - r = self.__origin__._argrepr() + return par_repr + r = self.__origin__._arg_repr() for i in range(len(self.__args__)): # replace free parameters with args par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) From 85209842f046639a09d61cb8b73b055ea3606bb1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 10:03:26 +0200 Subject: [PATCH 6/8] Remove unnecessary recursion --- python2/typing.py | 18 +++++++++++++----- src/typing.py | 36 ++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index f5395a89..160d444a 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1125,11 +1125,19 @@ def _arg_repr(self): if not self.__parameters__: return '' return par_repr - r = self.__origin__._arg_repr() - for i in range(len(self.__args__)): # replace free parameters with args - par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) - r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) - return r.format(*map(_type_repr, self.__args__)) + + current = self + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + r = '[%s]' % (', '.join(map(_type_repr, orig_chain[-1].__origin__.__parameters__))) + for tp in reversed(orig_chain): + for i in range(len(tp.__args__)): # replace free parameters with args + par = stdlib_re.escape(_type_repr(tp.__origin__.__parameters__[i])) + r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) + r = r.format(*map(_type_repr, tp.__args__)) + return r def __eq__(self, other): if not isinstance(other, GenericMeta): diff --git a/src/typing.py b/src/typing.py index 264a2ef3..668e9734 100644 --- a/src/typing.py +++ b/src/typing.py @@ -878,6 +878,10 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _arg_repr(cls): + return '[%s]' % (', '.join(map(_type_repr, cls.__parameters__))) + + def _next_in_mro(cls): """Helper for Generic.__new__. @@ -1006,21 +1010,25 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - return super().__repr__() + self._arg_repr() - - def _arg_repr(self): - par_repr = '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) - if self.__origin__ in [Generic, _Protocol]: - return par_repr + r = super().__repr__() if self.__origin__ is None: - if not self.__parameters__: - return '' - return par_repr - r = self.__origin__._arg_repr() - for i in range(len(self.__args__)): # replace free parameters with args - par = stdlib_re.escape(_type_repr(self.__origin__.__parameters__[i])) - r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) - return r.format(*map(_type_repr, self.__args__)) + return r + if self.__origin__ in [Generic, _Protocol]: + return r + _arg_repr(self) + + current = self + orig_chain = [] + while current is not None: + orig_chain.append(current) + current = current.__origin__ + + ar = _arg_repr(orig_chain.pop()) + for cls in reversed(orig_chain): + for i in range(len(cls.__args__)): # replace free parameters with args + par = stdlib_re.escape(_type_repr(cls.__origin__.__parameters__[i])) + ar = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, ar) + ar = ar.format(*map(_type_repr, cls.__args__)) + return r + ar def __eq__(self, other): if not isinstance(other, GenericMeta): From ff35a80b35b44348234801899e70b14444fc6257 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 11:34:42 +0200 Subject: [PATCH 7/8] Add replacement algorithm to Generic --- src/typing.py | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/typing.py b/src/typing.py index 668e9734..ebe16a02 100644 --- a/src/typing.py +++ b/src/typing.py @@ -878,10 +878,6 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) -def _arg_repr(cls): - return '[%s]' % (', '.join(map(_type_repr, cls.__parameters__))) - - def _next_in_mro(cls): """Helper for Generic.__new__. @@ -1009,26 +1005,33 @@ def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: _get_type_vars(self.__parameters__, tvars) + def _subs_repr(self, tvars, args): + assert len(tvars) == len(args) + # Construct the chain of __origin__'s. + current = self.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + str_args = [] + for arg in self.__args__: + str_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for cls in orig_chain: + new_str_args = [] + for i, arg in enumerate(cls.__args__): + new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) + str_args = new_str_args + return super().__repr__() + '[%s]' % ', '.join(str_args) + def __repr__(self): r = super().__repr__() if self.__origin__ is None: return r if self.__origin__ in [Generic, _Protocol]: - return r + _arg_repr(self) - - current = self - orig_chain = [] - while current is not None: - orig_chain.append(current) - current = current.__origin__ - - ar = _arg_repr(orig_chain.pop()) - for cls in reversed(orig_chain): - for i in range(len(cls.__args__)): # replace free parameters with args - par = stdlib_re.escape(_type_repr(cls.__origin__.__parameters__[i])) - ar = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, ar) - ar = ar.format(*map(_type_repr, cls.__args__)) - return r + ar + return r + '[%s]' % ', '.join(map(_type_repr, self.__parameters__)) + return self._subs_repr([], []) # Not necessary to replace anything def __eq__(self, other): if not isinstance(other, GenericMeta): @@ -1099,6 +1102,16 @@ def __instancecheck__(self, instance): return issubclass(instance.__class__, self) +def _replace_arg(arg, tvars, args): + if isinstance(arg, GenericMeta): + return arg._subs_repr(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg.__name__ == tvar.__name__: + return args[i] + return _type_repr(arg) + + # Prevent checks for Generic to crash when defining Generic. Generic = None From 232f80ad41efbbaba9ccb546d85ec58a83beab1a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 13:18:54 +0200 Subject: [PATCH 8/8] Add _subs_repr to other types; fix minor bugs --- python2/test_typing.py | 8 ++--- python2/typing.py | 78 +++++++++++++++++++++++++++--------------- src/test_typing.py | 8 ++--- src/typing.py | 67 +++++++++++++++++++++--------------- 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index bb5a3425..866f1b50 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -521,9 +521,9 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + __name__ + '.' + 'SimpleMapping') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + __name__ + '.' + 'MySimpleMapping') def test_chain_repr(self): T = TypeVar('T') @@ -554,7 +554,7 @@ def test_new_repr(self): U = TypeVar('U', covariant=True) S = TypeVar('S') - self.assertEqual(repr(List), 'typing.List[~T]') + self.assertEqual(repr(List), 'typing.List') self.assertEqual(repr(List[T]), 'typing.List[~T]') self.assertEqual(repr(List[U]), 'typing.List[+U]') self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') @@ -664,7 +664,7 @@ class C(Generic[T]): if not PY32: self.assertEqual(C.__qualname__, 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C[~T]') + self.assertEqual(repr(C).split('.')[-1], 'C') X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: diff --git a/python2/typing.py b/python2/typing.py index 160d444a..1cd7cb8a 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -276,8 +276,8 @@ def __getitem__(self, parameter): if not issubclass(parameter, self.type_var.__constraints__): raise TypeError("%s is not a valid substitution for %s." % (parameter, self.type_var)) - if isinstance(parameter, TypeVar): - raise TypeError("%s cannot be re-parameterized." % self.type_var) + if isinstance(parameter, TypeVar) and parameter is not self.type_var: + raise TypeError("%s cannot be re-parameterized." % self) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) @@ -398,12 +398,15 @@ def _eval_type(self, globalns, localns): def _get_type_vars(self, tvars): if self.__type__: - _get_type_vars(self.__type__, tvars) + _get_type_vars([self.__type__], tvars) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super(_ClassVar, self).__repr__() if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) + r += '[{}]'.format(_replace_arg(self.__type__, tvars, args)) return r def __hash__(self): @@ -703,9 +706,12 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__union_params__, tvars) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super(_Union, self).__repr__() if self.__union_params__: - r += '[%s]' % (', '.join(_type_repr(t) + r += '[%s]' % (', '.join(_replace_arg(t, tvars, args) for t in self.__union_params__)) return r @@ -805,9 +811,12 @@ def _eval_type(self, globalns, localns): return self.__class__(p, _root=True) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super(_Tuple, self).__repr__() if self.__tuple_params__ is not None: - params = [_type_repr(p) for p in self.__tuple_params__] + params = [_replace_arg(p, tvars, args) for p in self.__tuple_params__] if self.__tuple_use_ellipsis__: params.append('...') if not params: @@ -898,6 +907,8 @@ def __init__(self, args=None, result=None, _root=False): def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: _get_type_vars(self.__args__, tvars) + if self.__result__: + _get_type_vars([self.__result__], tvars) def _eval_type(self, globalns, localns): if self.__args__ is None and self.__result__ is None: @@ -913,14 +924,17 @@ def _eval_type(self, globalns, localns): return self.__class__(args=args, result=result, _root=True) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super(_Callable, self).__repr__() if self.__args__ is not None or self.__result__ is not None: if self.__args__ is Ellipsis: args_r = '...' else: - args_r = '[%s]' % ', '.join(_type_repr(t) + args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) for t in self.__args__) - r += '[%s, %s]' % (args_r, _type_repr(self.__result__)) + r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) return r def __getitem__(self, parameters): @@ -985,6 +999,16 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _replace_arg(arg, tvars, args): + if hasattr(arg, '_subs_repr'): + return arg._subs_repr(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg.__name__ == tvar.__name__: + return args[i] + return _type_repr(arg) + + def _next_in_mro(cls): """Helper for Generic.__new__. @@ -1115,29 +1139,29 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def __repr__(self): - return super(GenericMeta, self).__repr__() + self._arg_repr() - - def _arg_repr(self): - par_repr = '[%s]' % (', '.join(map(_type_repr, self.__parameters__))) - if self.__origin__ in [Generic, _Protocol]: - return par_repr if self.__origin__ is None: - if not self.__parameters__: - return '' - return par_repr + return super(GenericMeta, self).__repr__() + return self._subs_repr([], []) - current = self + def _subs_repr(self, tvars, args): + assert len(tvars) == len(args) + # Construct the chain of __origin__'s. + current = self.__origin__ orig_chain = [] while current.__origin__ is not None: orig_chain.append(current) current = current.__origin__ - r = '[%s]' % (', '.join(map(_type_repr, orig_chain[-1].__origin__.__parameters__))) - for tp in reversed(orig_chain): - for i in range(len(tp.__args__)): # replace free parameters with args - par = stdlib_re.escape(_type_repr(tp.__origin__.__parameters__[i])) - r = stdlib_re.sub(par + '(?=[,\]])', '{%r}' % i, r) - r = r.format(*map(_type_repr, tp.__args__)) - return r + # Replace type variables in __args__ if asked ... + str_args = [] + for arg in self.__args__: + str_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for cls in orig_chain: + new_str_args = [] + for i, arg in enumerate(cls.__args__): + new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) + str_args = new_str_args + return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(str_args) def __eq__(self, other): if not isinstance(other, GenericMeta): @@ -1170,11 +1194,11 @@ def __getitem__(self, params): raise TypeError( "Parameters to Generic[...] must all be unique") tvars = params - args = None + args = params elif self is _Protocol: # _Protocol is internal, don't check anything. tvars = params - args = None + args = params elif self.__origin__ in (Generic, _Protocol): # Can't subscript Generic[...] or _Protocol[...]. raise TypeError("Cannot subscript already-subscripted %s" % diff --git a/src/test_typing.py b/src/test_typing.py index 15af5fb5..052e8bcf 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -548,9 +548,9 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + __name__ + '.' + 'SimpleMapping') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + __name__ + '.' + 'MySimpleMapping') def test_chain_repr(self): T = TypeVar('T') @@ -581,7 +581,7 @@ def test_new_repr(self): U = TypeVar('U', covariant=True) S = TypeVar('S') - self.assertEqual(repr(List), 'typing.List[~T]') + self.assertEqual(repr(List), 'typing.List') self.assertEqual(repr(List[T]), 'typing.List[~T]') self.assertEqual(repr(List[U]), 'typing.List[+U]') self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') @@ -691,7 +691,7 @@ class C(Generic[T]): if not PY32: self.assertEqual(C.__qualname__, 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C[~T]') + self.assertEqual(repr(C).split('.')[-1], 'C') X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: diff --git a/src/typing.py b/src/typing.py index ebe16a02..1f95a5dc 100644 --- a/src/typing.py +++ b/src/typing.py @@ -292,8 +292,8 @@ def __getitem__(self, parameter): if not issubclass(parameter, self.type_var.__constraints__): raise TypeError("%s is not a valid substitution for %s." % (parameter, self.type_var)) - if isinstance(parameter, TypeVar): - raise TypeError("%s cannot be re-parameterized." % self.type_var) + if isinstance(parameter, TypeVar) and parameter is not self.type_var: + raise TypeError("%s cannot be re-parameterized." % self) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) @@ -622,9 +622,12 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__union_params__, tvars) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super().__repr__() if self.__union_params__: - r += '[%s]' % (', '.join(_type_repr(t) + r += '[%s]' % (', '.join(_replace_arg(t, tvars, args) for t in self.__union_params__)) return r @@ -706,9 +709,12 @@ def _eval_type(self, globalns, localns): return self.__class__(p, _root=True) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super().__repr__() if self.__tuple_params__ is not None: - params = [_type_repr(p) for p in self.__tuple_params__] + params = [_replace_arg(p, tvars, args) for p in self.__tuple_params__] if self.__tuple_use_ellipsis__: params.append('...') if not params: @@ -791,6 +797,8 @@ def __init__(self, args=None, result=None, _root=False): def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: _get_type_vars(self.__args__, tvars) + if self.__result__: + _get_type_vars([self.__result__], tvars) def _eval_type(self, globalns, localns): if self.__args__ is None and self.__result__ is None: @@ -806,14 +814,17 @@ def _eval_type(self, globalns, localns): return self.__class__(args, result, _root=True) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super().__repr__() if self.__args__ is not None or self.__result__ is not None: if self.__args__ is Ellipsis: args_r = '...' else: - args_r = '[%s]' % ', '.join(_type_repr(t) + args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) for t in self.__args__) - r += '[%s, %s]' % (args_r, _type_repr(self.__result__)) + r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) return r def __getitem__(self, parameters): @@ -878,6 +889,16 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _replace_arg(arg, tvars, args): + if hasattr(arg, '_subs_repr'): + return arg._subs_repr(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg.__name__ == tvar.__name__: + return args[i] + return _type_repr(arg) + + def _next_in_mro(cls): """Helper for Generic.__new__. @@ -1005,6 +1026,11 @@ def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: _get_type_vars(self.__parameters__, tvars) + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._subs_repr([], []) + def _subs_repr(self, tvars, args): assert len(tvars) == len(args) # Construct the chain of __origin__'s. @@ -1025,14 +1051,6 @@ def _subs_repr(self, tvars, args): str_args = new_str_args return super().__repr__() + '[%s]' % ', '.join(str_args) - def __repr__(self): - r = super().__repr__() - if self.__origin__ is None: - return r - if self.__origin__ in [Generic, _Protocol]: - return r + '[%s]' % ', '.join(map(_type_repr, self.__parameters__)) - return self._subs_repr([], []) # Not necessary to replace anything - def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented @@ -1064,11 +1082,11 @@ def __getitem__(self, params): raise TypeError( "Parameters to Generic[...] must all be unique") tvars = params - args = None + args = params elif self is _Protocol: # _Protocol is internal, don't check anything. tvars = params - args = None + args = params elif self.__origin__ in (Generic, _Protocol): # Can't subscript Generic[...] or _Protocol[...]. raise TypeError("Cannot subscript already-subscripted %s" % @@ -1102,16 +1120,6 @@ def __instancecheck__(self, instance): return issubclass(instance.__class__, self) -def _replace_arg(arg, tvars, args): - if isinstance(arg, GenericMeta): - return arg._subs_repr(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg.__name__ == tvar.__name__: - return args[i] - return _type_repr(arg) - - # Prevent checks for Generic to crash when defining Generic. Generic = None @@ -1188,12 +1196,15 @@ def _eval_type(self, globalns, localns): def _get_type_vars(self, tvars): if self.__type__: - _get_type_vars(self.__type__, tvars) + _get_type_vars([self.__type__], tvars) def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): r = super().__repr__() if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) + r += '[{}]'.format(_replace_arg(self.__type__, tvars, args)) return r def __hash__(self):