Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7d73596
Started #136
ilevkivskyi Sep 18, 2016
20de824
Started #136
ilevkivskyi Sep 18, 2016
8e6f0a6
More work on #136
ilevkivskyi Sep 18, 2016
bcde4c6
Finish first round of #136
ilevkivskyi Sep 18, 2016
9987bb2
Shorter error messages
ilevkivskyi Sep 18, 2016
90d72da
Added lru_cache for Generic[]
ilevkivskyi Sep 18, 2016
603ff6d
Flexible cache for Generic[]
ilevkivskyi Sep 18, 2016
fa4a681
Skip GenericMeta in FrozenSetMeta MRO
ilevkivskyi Sep 18, 2016
cc99972
Revert "Skip GenericMeta in FrozenSetMeta MRO"
ilevkivskyi Sep 18, 2016
b587dc0
Remove redundant metaclass for frozenset
ilevkivskyi Sep 18, 2016
e25b303
Some formatting
ilevkivskyi Sep 18, 2016
3ccb7a9
Fix async/await tests
ilevkivskyi Sep 18, 2016
0606059
Fixed ContextManger test
ilevkivskyi Sep 18, 2016
8b909d6
Be consistent about _TypeAlias
ilevkivskyi Sep 18, 2016
554f2ed
Removed debugging print()
ilevkivskyi Sep 18, 2016
11189b0
Simplified code
ilevkivskyi Sep 19, 2016
4c91c3b
Increased cache size to 50; start backporting changes to Python 2
ilevkivskyi Sep 22, 2016
1920009
Backport Union and Optional to Python 2
ilevkivskyi Sep 23, 2016
f003eca
Finished backport to Python 2; some polishing is needed
ilevkivskyi Sep 23, 2016
e130c77
Added many unit tests for bugs fixed by PR
ilevkivskyi Sep 24, 2016
fe2d3b7
Updated docstrings and other minor things
ilevkivskyi Sep 24, 2016
e4cebff
Optimized caching + first part of response to comments
ilevkivskyi Sep 25, 2016
9b443cb
Second part of response to comments. Need to port all comments etc. t…
ilevkivskyi Sep 26, 2016
bd160a1
Start backporting comments to Python2; improve caching
ilevkivskyi Sep 26, 2016
80ce513
Get rid of frozensetmeta, it causes random failures
ilevkivskyi Sep 26, 2016
c4f11e8
Backported everything to Python 2
Sep 26, 2016
230a500
Do not duplicate real errors while caching
ilevkivskyi Sep 26, 2016
e8c85b2
Merge remote-tracking branch 'origin/master'
ilevkivskyi Sep 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Second part of response to comments. Need to port all comments etc. t…
…o Python 2
  • Loading branch information
ilevkivskyi committed Sep 26, 2016
commit 9b443cb45934b8e515c62ab5ad571b5571008139
114 changes: 78 additions & 36 deletions src/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,15 @@ def __repr__(self):
return '%s.%s' % (self.__module__, qname)


class TypingBase(metaclass=TypingMeta, _root=True):
class _TypingBase(metaclass=TypingMeta, _root=True):
"""Indicator of special typing constructs."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a __slots__ here too, otherwise the __slots__ in the subclass is pointless. Also all the final classes (and probably also those inheriting from TypingBase) should have their own __slots__.

(I guess it's a little-known fact that __slots__ only suppresses the __dict__ when used for every class in the MRO...)

__slots__ = ()


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one blank line is required.

def __init__(self, *args, **kwds):
pass

def __new__(cls, *args, **kwds):
"""Constructor.

Expand All @@ -154,7 +160,7 @@ def __new__(cls, *args, **kwds):
raise TypeError("Cannot subclass %r" % cls)
return object.__new__(cls)

@gvanrossum gvanrossum Sep 26, 2016 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this use super()?


# things that are not classes also need these
# Things that are not classes also need these.
def _eval_type(self, globalns, localns):
return self

Expand All @@ -170,7 +176,7 @@ def __call__(self, *args, **kwds):
raise TypeError("Cannot instantiate %r" % type(self))


class Final(TypingBase, _root=True):
class _FinalTypingBase(_TypingBase, _root=True):
"""Mix-in class to prevent instantiation."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd update the docstring to hint that it prevents instantiation unless _root=True is given.


__slots__ = ()
Expand All @@ -182,10 +188,15 @@ def __new__(cls, *args, _root=False, **kwds):
raise TypeError("Cannot instantiate %r" % cls)


class _ForwardRef(TypingBase, _root=True):
class _ForwardRef(_TypingBase, _root=True):
"""Wrapper to hold a forward reference."""

__slots__ = ('__forward_arg__', '__forward_code__',
'__forward_evaluated__', '__forward_value__',
'__forward_frame__')

def __init__(self, arg):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but I still think it should call super().

super().__init__(arg)
if not isinstance(arg, str):
raise TypeError('ForwardRef must be a string -- got %r' % (arg,))
try:
Expand Down Expand Up @@ -221,10 +232,11 @@ def _eval_type(self, globalns, localns):
def __eq__(self, other):
if not isinstance(other, _ForwardRef):
return NotImplemented
return self.__forward_arg__ == other.__forward_arg__
return (self.__forward_arg__ == other.__forward_arg__ and
self.__forward_frame__ == other.__forward_frame__)

def __hash__(self):
return hash(self.__forward_arg__)
return hash((self.__forward_arg__, self.__forward_frame__))

def __instancecheck__(self, obj):
raise TypeError("Forward references cannot be used with isinstance().")
Expand All @@ -236,7 +248,7 @@ def __repr__(self):
return '_ForwardRef(%r)' % (self.__forward_arg__,)


class _TypeAlias(TypingBase, _root=True):
class _TypeAlias(_TypingBase, _root=True):
"""Internal helper class for defining generic variants of concrete types.

Note that this is not a type; let's call it a pseudo-type. It cannot
Expand All @@ -261,8 +273,9 @@ def __init__(self, name, type_var, impl_type, type_checker):
assert isinstance(name, str), repr(name)
assert isinstance(impl_type, type), repr(impl_type)
assert not isinstance(impl_type, TypingMeta), repr(impl_type)
assert isinstance(type_var, (type, _TypingBase))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add repr(type_var) as the message?

self.name = name
self.type_var = _type_check(type_var, "Type alias accepts only types")
self.type_var = type_var
self.impl_type = impl_type
self.type_checker = type_checker

Expand Down Expand Up @@ -304,7 +317,7 @@ def __subclasscheck__(self, cls):

def _get_type_vars(types, tvars):
for t in types:
if isinstance(t, TypingMeta) or isinstance(t, TypingBase):
if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
t._get_type_vars(tvars)


Expand All @@ -315,7 +328,7 @@ def _type_vars(types):


def _eval_type(t, globalns, localns):
if isinstance(t, TypingMeta) or isinstance(t, TypingBase):
if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
return t._eval_type(globalns, localns)
else:
return t
Expand All @@ -337,7 +350,7 @@ def _type_check(arg, msg):
return type(None)
if isinstance(arg, str):
arg = _ForwardRef(arg)
if not isinstance(arg, (type, TypingBase)) and not callable(arg):
if not isinstance(arg, (type, _TypingBase)) and not callable(arg):
raise TypeError(msg + " Got %.100r." % (arg,))
return arg

Expand All @@ -359,14 +372,16 @@ def _type_repr(obj):
return repr(obj)


class _Any(Final, _root=True):
class _Any(_FinalTypingBase, _root=True):
"""Special type indicating an unconstrained type.

- Any object is an instance of Any.
- Any class is a subclass of Any.
- As a special case, Any and object are subclasses of each other.
"""

__slots__ = ()

def __instancecheck__(self, obj):
raise TypeError("Any cannot be used with isinstance().")

Expand All @@ -377,7 +392,7 @@ def __subclasscheck__(self, cls):
Any = _Any(_root=True)


class TypeVar(TypingBase, _root=True):
class TypeVar(_TypingBase, _root=True):
"""Type variable.

Usage::
Expand Down Expand Up @@ -422,8 +437,13 @@ def longest(x: A, y: A) -> A:
A.__constraints__ == (str, bytes)
"""

__slots__ = ('__name__', '__bound__', '__constraints__',
'__covariant__', '__contravariant__')

def __init__(self, name, *constraints, bound=None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'd still like the super() method called.

covariant=False, contravariant=False):
super().__init__(name, *constraints, bound=bound,
covariant=covariant, contravariant=contravariant)
self.__name__ = name
if covariant and contravariant:
raise ValueError("Bivariant types are not supported.")
Expand Down Expand Up @@ -486,7 +506,7 @@ def inner(*args, **kwargs):
return inner


class _Union(Final, _root=True):
class _Union(_FinalTypingBase, _root=True):
"""Union type; Union[X, Y] means either X or Y.

To define a union, use e.g. Union[int, str]. Details:
Expand Down Expand Up @@ -538,6 +558,8 @@ class Manager(Employee): pass
- You can use Optional[X] as a shorthand for Union[X, None].
"""

__slots__ = ('__union_params__', '__union_set_params__')

def __new__(cls, parameters=None, *args, _root=False):
self = super().__new__(cls, parameters, *args, _root=_root)
if parameters is None:
Expand Down Expand Up @@ -636,12 +658,14 @@ def __subclasscheck__(self, cls):
Union = _Union(_root=True)


class _Optional(Final, _root=True):
class _Optional(_FinalTypingBase, _root=True):
"""Optional type.

Optional[X] is equivalent to Union[X, type(None)].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While type(None) is technically correct I'm not sure how much it adds to mention it here (especially since Union[x, None] and Union[x, type(None)] are always equivalent :-).

"""

__slots__ = ()

def __getitem__(self, arg):
arg = _type_check(arg, "Optional[t] requires a single type.")
return Union[arg, type(None)]
Expand All @@ -650,7 +674,7 @@ def __getitem__(self, arg):
Optional = _Optional(_root=True)


class _Tuple(Final, _root=True):
class _Tuple(_FinalTypingBase, _root=True):
"""Tuple type; Tuple[X, Y] is the cross-product type of X and Y.

Example: Tuple[T1, T2] is a tuple of two elements corresponding
Expand All @@ -660,6 +684,8 @@ class _Tuple(Final, _root=True):
To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
"""

__slots__ = ('__tuple_params__', '__tuple_use_ellipsis__')

def __init__(self, parameters=None,
use_ellipsis=False, _root=False):
self.__tuple_params__ = parameters
Expand Down Expand Up @@ -732,7 +758,7 @@ def __subclasscheck__(self, cls):
Tuple = _Tuple(_root=True)


class _Callable(Final, _root=True):
class _Callable(_FinalTypingBase, _root=True):
"""Callable type; Callable[[int], str] is a function of (int) -> str.

The subscription syntax must always be used with exactly two
Expand All @@ -743,6 +769,8 @@ class _Callable(Final, _root=True):
such function types are rarely used as callback types.
"""

__slots__ = ('__args__', '__result__')

def __init__(self, args=None, result=None, _root=False):
if args is None and result is None:
pass
Expand Down Expand Up @@ -1055,7 +1083,7 @@ def __new__(cls, *args, **kwds):
return obj


class _ClassVar(Final, _root=True):
class _ClassVar(_FinalTypingBase, _root=True):
"""Special type construct to mark class variables.

An annotation wrapped in ClassVar indicates that a given
Expand All @@ -1072,21 +1100,25 @@ class Starship:
be used with isinstance() or issubclass().
"""

__slots__ = ('__type__',)

def __init__(self, tp=None, **kwds):
self.__type__ = tp

def __getitem__(self, item):
cls = type(self)
if self.__type__ is None:
return cls(_type_check(item,
'{} accepts only types.'.format(cls.__name__[1:])),
'{} accepts only single type.'.format(cls.__name__[1:])),
_root=True)
raise TypeError('{} cannot be further subscripted'
.format(cls.__name__[1:]))

def _eval_type(self, globalns, localns):
return type(self)(_eval_type(self.__type__, globalns, localns),
_root=True)
new_tp = _eval_type(self.__type__, globalns, localns)
if new_tp == self.__type__:
return self
return type(self)(new_tp, _root=True)

def _get_type_vars(self, tvars):
if self.__type__:
Expand Down Expand Up @@ -1547,59 +1579,61 @@ class Collection(Sized, Iterable[T_co], Container[T_co],
if hasattr(collections_abc, 'Collection'):
class AbstractSet(Collection[T_co],
extra=collections_abc.Set):
pass
__slots__ = ()
else:
class AbstractSet(Sized, Iterable[T_co], Container[T_co],
extra=collections_abc.Set):
pass
__slots__ = ()


class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet):
pass
__slots__ = ()


# NOTE: It is only covariant in the value type.
if hasattr(collections_abc, 'Collection'):
class Mapping(Collection[KT], Generic[KT, VT_co],
extra=collections_abc.Mapping):
pass
__slots__ = ()
else:
class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co],
extra=collections_abc.Mapping):
pass
__slots__ = ()


class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping):
pass
__slots__ = ()

if hasattr(collections_abc, 'Reversible'):
if hasattr(collections_abc, 'Collection'):
class Sequence(Reversible[T_co], Collection[T_co],
extra=collections_abc.Sequence):
pass
__slots__ = ()
else:
class Sequence(Sized, Reversible[T_co], Container[T_co],
extra=collections_abc.Sequence):
pass
__slots__ = ()
else:
class Sequence(Sized, Iterable[T_co], Container[T_co],
extra=collections_abc.Sequence):
pass
__slots__ = ()


class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence):
pass
__slots__ = ()


class ByteString(Sequence[int], extra=collections_abc.ByteString):
pass
__slots__ = ()


ByteString.register(type(memoryview(b'')))


class List(list, MutableSequence[T], extra=list):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, List):
raise TypeError("Type List cannot be instantiated; "
Expand All @@ -1609,6 +1643,8 @@ def __new__(cls, *args, **kwds):

class Set(set, MutableSet[T], extra=set):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, Set):
raise TypeError("Type Set cannot be instantiated; "
Expand Down Expand Up @@ -1642,23 +1678,23 @@ def __new__(cls, *args, **kwds):


class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView):
pass
__slots__ = ()


class KeysView(MappingView[KT], AbstractSet[KT],
extra=collections_abc.KeysView):
pass
__slots__ = ()


class ItemsView(MappingView[Tuple[KT, VT_co]],
AbstractSet[Tuple[KT, VT_co]],
Generic[KT, VT_co],
extra=collections_abc.ItemsView):
pass
__slots__ = ()


class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
pass
__slots__ = ()


if hasattr(contextlib, 'AbstractContextManager'):
Expand All @@ -1669,6 +1705,8 @@ class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):

class Dict(dict, MutableMapping[KT, VT], extra=dict):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, Dict):
raise TypeError("Type Dict cannot be instantiated; "
Expand All @@ -1678,6 +1716,8 @@ def __new__(cls, *args, **kwds):
class DefaultDict(collections.defaultdict, MutableMapping[KT, VT],
extra=collections.defaultdict):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, DefaultDict):
raise TypeError("Type DefaultDict cannot be instantiated; "
Expand Down Expand Up @@ -1733,6 +1773,8 @@ def new_user(user_class: Type[U]) -> U:
At this point the type checker knows that joe has type BasicUser.
"""

__slots__ = ()


def _make_nmtuple(name, types):
nm_tpl = collections.namedtuple(name, [n for n, t in types])
Expand Down