Skip to content

Commit 50d2b4d

Browse files
ilevkivskyigvanrossum
authored andcommitted
Refactor Union, Tuple, and Callable (#308)
Fixes #301 now ``List[Tuple[T, T]][int] == List[Tuple[int, int]]`` Fixes #298 now ``Table = Tuple[T, List[T]]`` can be used as generic type alias (as in PEP 484 example) Fixes #299 now ``class MyTup(Tuple[int, ...]): ...`` is allowed Fixes #156 (well, it does not substitute the annotations, but makes this task simple even in complex cases, see example in tests) Also this PR fixes some minor things that I have found while working on this: * ``List[Union]`` (with bare ``Union``, i.e. without arguments), ``List[Optional]``, ``List[Generic[T]]``, and ``List[ClassVar[int]]`` are not valid and are prohibited now. * ``Generic`` did not evaluate forward references when asked, now it does. * ``__qualname__`` was not copied on generic class subscription. * Type was not erased on instantiation of subclasses of concrete containers (``List``, ``Set``, etc). * There was an obscure bug in Python 2: sometimes ``_abc_registry`` was erased on instantiation. The main idea of this PR is to fix the issues mentioned at the top by reusing the existing code. Namely, I pulled flattening and removing duplicates code from ``_Union`` and the tree calculation function ``_subs_tree`` from ``GenericMeta``. As well I moved ``Tuple`` and ``Callable`` _after_ ``GenericMeta`` and made them inherit the latter. So that now all types that could be generic store their info in common way using ``__origin__``, ``__parameters__``, ``__args__``. I tried to polish this, to be sure that nothing was broken in the process of "refactoring" (also to improve speed). There is no recursion, the substitution tree is recalculated only when necessary. Also I added a lot of tests and many comments/docstrings (also for things added in my recent PRs).
1 parent 3ca1682 commit 50d2b4d

File tree

4 files changed

+1072
-730
lines changed

4 files changed

+1072
-730
lines changed

python2/test_typing.py

+122-21
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,9 @@ def test_union_unique(self):
141141
self.assertEqual(Union[X, X], X)
142142
self.assertNotEqual(Union[X, int], Union[X])
143143
self.assertNotEqual(Union[X, int], Union[int])
144-
self.assertEqual(Union[X, int].__union_params__, (X, int))
145-
self.assertEqual(Union[X, int].__union_set_params__, {X, int})
144+
self.assertEqual(Union[X, int].__args__, (X, int))
145+
self.assertEqual(Union[X, int].__parameters__, (X,))
146+
self.assertIs(Union[X, int].__origin__, Union)
146147

147148
def test_union_constrained(self):
148149
A = TypeVar('A', str, bytes)
@@ -308,8 +309,6 @@ def Elem(*args):
308309
class TupleTests(BaseTestCase):
309310

310311
def test_basics(self):
311-
with self.assertRaises(TypeError):
312-
issubclass(Tuple[int, str], Tuple)
313312
with self.assertRaises(TypeError):
314313
issubclass(Tuple, Tuple[int, str])
315314
with self.assertRaises(TypeError):
@@ -364,22 +363,6 @@ def test_eq_hash(self):
364363
self.assertNotEqual(Callable[[int], int], Callable[[], int])
365364
self.assertNotEqual(Callable[[int], int], Callable)
366365

367-
def test_cannot_subclass(self):
368-
with self.assertRaises(TypeError):
369-
370-
class C(Callable):
371-
pass
372-
373-
with self.assertRaises(TypeError):
374-
375-
class C(type(Callable)):
376-
pass
377-
378-
with self.assertRaises(TypeError):
379-
380-
class C(Callable[[int], int]):
381-
pass
382-
383366
def test_cannot_instantiate(self):
384367
with self.assertRaises(TypeError):
385368
Callable()
@@ -683,6 +666,124 @@ class D(C, List[T][U][V]): pass
683666
self.assertEqual(C.__orig_bases__, (List[T][U][V],))
684667
self.assertEqual(D.__orig_bases__, (C, List[T][U][V]))
685668

669+
def test_extended_generic_rules_eq(self):
670+
T = TypeVar('T')
671+
U = TypeVar('U')
672+
self.assertEqual(Tuple[T, T][int], Tuple[int, int])
673+
self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]])
674+
with self.assertRaises(TypeError):
675+
Tuple[T, int][()]
676+
with self.assertRaises(TypeError):
677+
Tuple[T, U][T, ...]
678+
679+
self.assertEqual(Union[T, int][int], int)
680+
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
681+
class Base(object): pass
682+
class Derived(Base): pass
683+
self.assertEqual(Union[T, Base][Derived], Base)
684+
with self.assertRaises(TypeError):
685+
Union[T, int][1]
686+
687+
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
688+
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])
689+
with self.assertRaises(TypeError):
690+
Callable[[T], U][..., int]
691+
with self.assertRaises(TypeError):
692+
Callable[[T], U][[], int]
693+
694+
def test_extended_generic_rules_repr(self):
695+
T = TypeVar('T')
696+
self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''),
697+
'Union[Tuple, Callable]')
698+
self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''),
699+
'Tuple')
700+
self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''),
701+
'Callable[..., Union[int, NoneType]]')
702+
self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''),
703+
'Callable[[], List[int]]')
704+
705+
def test_generic_forvard_ref(self):
706+
LLT = List[List['T']]
707+
T = TypeVar('T')
708+
self.assertEqual(typing._eval_type(LLT, globals(), locals()), List[List[T]])
709+
TTE = Tuple[T, ...]
710+
self.assertIs(typing._eval_type(TTE, globals(), locals()), Tuple[T, ...])
711+
712+
def test_extended_generic_rules_subclassing(self):
713+
class T1(Tuple[T, KT]): pass
714+
class T2(Tuple[T, ...]): pass
715+
class C1(Callable[[T], T]): pass
716+
class C2(Callable[..., int]):
717+
def __call__(self):
718+
return None
719+
720+
self.assertEqual(T1.__parameters__, (T, KT))
721+
self.assertEqual(T1[int, str].__args__, (int, str))
722+
self.assertEqual(T1[int, T].__origin__, T1)
723+
724+
self.assertEqual(T2.__parameters__, (T,))
725+
with self.assertRaises(TypeError):
726+
T1[int]
727+
with self.assertRaises(TypeError):
728+
T2[int, str]
729+
730+
self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]')
731+
self.assertEqual(C2.__parameters__, ())
732+
self.assertIsInstance(C2(), collections_abc.Callable)
733+
self.assertIsSubclass(C2, collections_abc.Callable)
734+
self.assertIsSubclass(C1, collections_abc.Callable)
735+
self.assertIsInstance(T1(), tuple)
736+
self.assertIsSubclass(T2, tuple)
737+
self.assertIsSubclass(Tuple[int, ...], typing.Sequence)
738+
self.assertIsSubclass(Tuple[int, ...], typing.Iterable)
739+
740+
def test_fail_with_bare_union(self):
741+
with self.assertRaises(TypeError):
742+
List[Union]
743+
with self.assertRaises(TypeError):
744+
Tuple[Optional]
745+
with self.assertRaises(TypeError):
746+
ClassVar[ClassVar]
747+
with self.assertRaises(TypeError):
748+
List[ClassVar[int]]
749+
750+
def test_fail_with_bare_generic(self):
751+
T = TypeVar('T')
752+
with self.assertRaises(TypeError):
753+
List[Generic]
754+
with self.assertRaises(TypeError):
755+
Tuple[Generic[T]]
756+
with self.assertRaises(TypeError):
757+
List[typing._Protocol]
758+
759+
def test_type_erasure_special(self):
760+
T = TypeVar('T')
761+
class MyTup(Tuple[T, T]): pass
762+
self.assertIs(MyTup[int]().__class__, MyTup)
763+
self.assertIs(MyTup[int]().__orig_class__, MyTup[int])
764+
class MyCall(Callable[..., T]):
765+
def __call__(self): return None
766+
self.assertIs(MyCall[T]().__class__, MyCall)
767+
self.assertIs(MyCall[T]().__orig_class__, MyCall[T])
768+
class MyDict(typing.Dict[T, T]): pass
769+
self.assertIs(MyDict[int]().__class__, MyDict)
770+
self.assertIs(MyDict[int]().__orig_class__, MyDict[int])
771+
class MyDef(typing.DefaultDict[str, T]): pass
772+
self.assertIs(MyDef[int]().__class__, MyDef)
773+
self.assertIs(MyDef[int]().__orig_class__, MyDef[int])
774+
775+
def test_all_repr_eq_any(self):
776+
objs = (getattr(typing, el) for el in typing.__all__)
777+
for obj in objs:
778+
self.assertNotEqual(repr(obj), '')
779+
self.assertEqual(obj, obj)
780+
if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1:
781+
self.assertEqual(obj[Any].__args__, (Any,))
782+
if isinstance(obj, type):
783+
for base in obj.__mro__:
784+
self.assertNotEqual(repr(base), '')
785+
self.assertEqual(base, base)
786+
686787
def test_pickle(self):
687788
global C # pickle wants to reference the class by name
688789
T = TypeVar('T')
@@ -724,7 +825,7 @@ class C(Generic[T]):
724825
X = C[int]
725826
self.assertEqual(X.__module__, __name__)
726827
if not PY32:
727-
self.assertEqual(X.__qualname__, 'C')
828+
self.assertTrue(X.__qualname__.endswith('.<locals>.C'))
728829
self.assertEqual(repr(X).split('.')[-1], 'C[int]')
729830

730831
class Y(C[int]):

0 commit comments

Comments
 (0)