Skip to content

Type[C] #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 8, 2016
Merged

Type[C] #1569

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b7ddf36
Tentative tests for Type[C].
May 18, 2016
eb61f1d
Pave the way for implementing Type[C].
May 20, 2016
7b52ac6
WIP: Introducing TypeType: Type[x] becomes TypeType(x).
May 20, 2016
1fd14f0
WIP: Fix/new tests; implement more subtype checks.
May 23, 2016
371e425
WIP: Towards supporting Type[T].
May 23, 2016
96696ff
Fix things so all tests pass!
May 24, 2016
aa37595
Some cleanup and fixup (not done).
May 24, 2016
d4745b0
Improve equivalence between type and Type.
May 25, 2016
cf4a0b7
Add support for method lookup from Type[].
May 25, 2016
81ee239
Print a proper error for unsupported Type[] args.
May 26, 2016
3d92049
Reject Type[U] where U's bound is a generic class.
May 26, 2016
5ab11d2
Support classes with @overload-ed __init__.
May 26, 2016
afb0482
Update our copies of typing.py to the latest from the python/typing r…
May 26, 2016
db30d59
Make Type[A] erase to Type[A], but Type[T] erases to Type[Any].
Jun 6, 2016
6029c9d
Special-case joining Type[] with builtins.type.
Jun 7, 2016
786ef96
Special-case joining Type[] with builtins.type.
Jun 7, 2016
ef247f2
Delete outdated XXX comment.
Jun 7, 2016
4162103
Finishing touch -- fix and test TypeAnalyser.visit_type_type().
Jun 7, 2016
26ff267
Added tests as requested.
Jun 7, 2016
256b31a
Remove comment asking for name for analyze_type_type_callee().
Jun 7, 2016
d224aff
Use self.default(self.s) instead of AnyType()/NonType() in join/meet.
Jun 7, 2016
d9c74a0
Add tests for overloaded __init__.
Jun 7, 2016
eaa9e0d
Add more tests. Fixed a bug in join() this found.
Jun 7, 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
31 changes: 31 additions & 0 deletions lib-typing/2.7/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from typing import Callable
from typing import Generic
from typing import cast
from typing import Type
from typing import NamedTuple
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
Expand Down Expand Up @@ -1110,6 +1111,36 @@ def __len__(self):
self.assertIsSubclass(MMC, typing.Mapping)


class TypeTests(BaseTestCase):

def test_type_basic(self):

class User(object): pass
class BasicUser(User): pass
class ProUser(User): pass

def new_user(user_class):
# type: (Type[User]) -> User
return user_class()

joe = new_user(BasicUser)

def test_type_typevar(self):

class User(object): pass
class BasicUser(User): pass
class ProUser(User): pass

global U
U = TypeVar('U', bound=User)

def new_user(user_class):
# type: (Type[U]) -> U
return user_class()

joe = new_user(BasicUser)


class NamedTupleTests(BaseTestCase):

def test_basics(self):
Expand Down
36 changes: 35 additions & 1 deletion lib-typing/2.7/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
'Callable',
'Generic',
'Optional',
'Tuple',
'Type',
'TypeVar',
'Union',
'Tuple',

# ABCs (from collections.abc).
'AbstractSet', # collections.abc.Set.
Expand Down Expand Up @@ -454,6 +455,7 @@ def __subclasscheck__(self, cls):


# Some unconstrained type variables. These are used by the container types.
# (These are not for export.)
T = TypeVar('T') # Any type.
KT = TypeVar('KT') # Key type.
VT = TypeVar('VT') # Value type.
Expand All @@ -463,6 +465,7 @@ def __subclasscheck__(self, cls):
T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.

# A useful type variable with constraints. This represents string types.
# (This one *is* for export!)
AnyStr = TypeVar('AnyStr', bytes, unicode)


Expand Down Expand Up @@ -1518,6 +1521,37 @@ def __new__(cls, *args, **kwds):
return super(Generator, cls).__new__(cls, *args, **kwds)


# Internal type variable used for Type[].
CT = TypeVar('CT', covariant=True, bound=type)


# This is not a real generic class. Don't use outside annotations.
class Type(type, Generic[CT]):
"""A special construct usable to annotate class objects.

For example, suppose we have the following classes::

class User: ... # Abstract base for User classes
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...

And a function that takes a class argument that's a subclass of
User and returns an instance of the corresponding class::

U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
user = user_class()
# (Here we could write the user object to a database)
return user

joe = new_user(BasicUser)

At this point the type checker knows that joe has type BasicUser.
"""
__extra__ = type


def NamedTuple(typename, fields):
"""Typed version of namedtuple.

Expand Down
28 changes: 28 additions & 0 deletions lib-typing/3.2/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import cast
from typing import get_type_hints
from typing import no_type_check, no_type_check_decorator
from typing import Type
from typing import NamedTuple
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
Expand Down Expand Up @@ -1373,6 +1374,33 @@ def manager():
self.assertNotIsInstance(42, typing.ContextManager)


class TypeTests(BaseTestCase):

def test_type_basic(self):

class User: pass
class BasicUser(User): pass
class ProUser(User): pass

def new_user(user_class: Type[User]) -> User:
return user_class()

joe = new_user(BasicUser)

def test_type_typevar(self):

class User: pass
class BasicUser(User): pass
class ProUser(User): pass

U = TypeVar('U', bound=User)

def new_user(user_class: Type[U]) -> U:
return user_class()

joe = new_user(BasicUser)


class NamedTupleTests(BaseTestCase):

def test_basics(self):
Expand Down
35 changes: 34 additions & 1 deletion lib-typing/3.2/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
'Callable',
'Generic',
'Optional',
'Tuple',
'Type',
'TypeVar',
'Union',
'Tuple',

# ABCs (from collections.abc).
'AbstractSet', # collections.abc.Set.
Expand Down Expand Up @@ -447,6 +448,7 @@ def __subclasscheck__(self, cls):


# Some unconstrained type variables. These are used by the container types.
# (These are not for export.)
T = TypeVar('T') # Any type.
KT = TypeVar('KT') # Key type.
VT = TypeVar('VT') # Value type.
Expand All @@ -456,6 +458,7 @@ def __subclasscheck__(self, cls):
T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.

# A useful type variable with constraints. This represents string types.
# (This one *is* for export!)
AnyStr = TypeVar('AnyStr', bytes, str)


Expand Down Expand Up @@ -1572,6 +1575,36 @@ def __new__(cls, *args, **kwds):
return super().__new__(cls, *args, **kwds)


# Internal type variable used for Type[].
CT = TypeVar('CT', covariant=True, bound=type)


# This is not a real generic class. Don't use outside annotations.
class Type(type, Generic[CT], extra=type):
"""A special construct usable to annotate class objects.

For example, suppose we have the following classes::

class User: ... # Abstract base for User classes
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...

And a function that takes a class argument that's a subclass of
User and returns an instance of the corresponding class::

U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
user = user_class()
# (Here we could write the user object to a database)
return user

joe = new_user(BasicUser)

At this point the type checker knows that joe has type BasicUser.
"""


def NamedTuple(typename, fields):
"""Typed version of namedtuple.

Expand Down
42 changes: 41 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mypy.types import (
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
TupleType, Instance, TypeVarType, ErasedType, UnionType,
PartialType, DeletedType, UnboundType
PartialType, DeletedType, UnboundType, TypeType
)
from mypy.nodes import (
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
Expand Down Expand Up @@ -277,9 +277,49 @@ def check_call(self, callee: Type, args: List[Node],
elif isinstance(callee, TypeVarType):
return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names,
callable_node, arg_messages)
elif isinstance(callee, TypeType):
# Pass the original Type[] as context since that's where errors should go.
item = self.analyze_type_type_callee(callee.item, callee)
return self.check_call(item, args, arg_kinds, context, arg_names,
callable_node, arg_messages)
else:
return self.msg.not_callable(callee, context), AnyType()

def analyze_type_type_callee(self, item: Type, context: Context) -> Type:
"""Analyze the callee X in X(...) where X is Type[item].

Return a Y that we can pass to check_call(Y, ...).
"""
if isinstance(item, AnyType):
return AnyType()
if isinstance(item, Instance):
return type_object_type(item.type, self.named_type)
if isinstance(item, UnionType):
return UnionType([self.analyze_type_type_callee(item, context)
for item in item.items], item.line)
if isinstance(item, TypeVarType):
# Pretend we're calling the typevar's upper bound,
# i.e. its constructor (a poor approximation for reality,
# but better than AnyType...), but replace the return type
# with typevar.
callee = self.analyze_type_type_callee(item.upper_bound, context)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the class is generic things get harder. Not sure what we should then, though. Maybe just reject calls to generic type objects for now.

if isinstance(callee, CallableType):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could also be Overloaded.

if callee.is_generic():
callee = None
else:
callee = callee.copy_modified(ret_type=item)
elif isinstance(callee, Overloaded):
if callee.items()[0].is_generic():
callee = None
else:
callee = Overloaded([c.copy_modified(ret_type=item)
for c in callee.items()])
if callee:
return callee

self.msg.unsupported_type_type(item, context)
return AnyType()

def infer_arg_types_in_context(self, callee: CallableType,
args: List[Node]) -> List[Type]:
"""Infer argument expression types using a callable type as context.
Expand Down
20 changes: 19 additions & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from mypy.types import (
Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarDef,
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType, DeletedType, NoneTyp
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType,
DeletedType, NoneTyp, TypeType
)
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context
from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, function_type, Decorator, OverloadedFuncDef
Expand Down Expand Up @@ -113,6 +114,23 @@ def analyze_member_access(name: str, typ: Type, node: Context, is_lvalue: bool,
elif isinstance(typ, DeletedType):
msg.deleted_as_rvalue(typ, node)
return AnyType()
elif isinstance(typ, TypeType):
# Similar to FunctionLike + is_type_obj() above.
item = None
if isinstance(typ.item, Instance):
item = typ.item
elif isinstance(typ.item, TypeVarType):
if isinstance(typ.item.upper_bound, Instance):
item = typ.item.upper_bound
if item:
result = analyze_class_attribute_access(item, name, node, is_lvalue,
builtin_type, not_ready_callback, msg)
if result:
return result
fallback = builtin_type('builtins.type')
return analyze_member_access(name, fallback, node, is_lvalue, is_super,
builtin_type, not_ready_callback, msg,
report_type=report_type)
return msg.has_no_attr(report_type, name, node)


Expand Down
17 changes: 14 additions & 3 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mypy.types import (
CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType,
Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType,
is_named_instance
TypeType, is_named_instance
)
from mypy.maptype import map_instance_to_supertype
from mypy import nodes
Expand Down Expand Up @@ -348,12 +348,23 @@ def infer_against_any(self, types: List[Type]) -> List[Constraint]:
res.extend(infer_constraints(t, AnyType(), self.direction))
return res

def visit_overloaded(self, type: Overloaded) -> List[Constraint]:
def visit_overloaded(self, template: Overloaded) -> List[Constraint]:
res = [] # type: List[Constraint]
for t in type.items():
for t in template.items():
res.extend(infer_constraints(t, self.actual, self.direction))
return res

def visit_type_type(self, template: TypeType) -> List[Constraint]:
if isinstance(self.actual, CallableType) and self.actual.is_type_obj():
return infer_constraints(template.item, self.actual.ret_type, self.direction)
elif isinstance(self.actual, Overloaded) and self.actual.is_type_obj():
return infer_constraints(template.item, self.actual.items()[0].ret_type,
self.direction)
elif isinstance(self.actual, TypeType):
return infer_constraints(template.item, self.actual.item, self.direction)
else:
return []


def neg_op(op: int) -> int:
"""Map SubtypeOf to SupertypeOf and vice versa."""
Expand Down
6 changes: 5 additions & 1 deletion mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from mypy.types import (
Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp,
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType,
PartialType, DeletedType, TypeTranslator, TypeList
PartialType, DeletedType, TypeTranslator, TypeList, TypeType
)


Expand All @@ -18,6 +18,7 @@ def erase_type(typ: Type) -> Type:
B[X] -> B[Any]
Tuple[A, B] -> tuple
Callable[...] -> Callable[[], None]
Type[X] -> Type[Any]
"""

return typ.accept(EraseTypeVisitor())
Expand Down Expand Up @@ -72,6 +73,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
def visit_union_type(self, t: UnionType) -> Type:
return AnyType() # XXX: return underlying type if only one?

def visit_type_type(self, t: TypeType) -> Type:
return TypeType(t.item.accept(self), line=t.line)


def erase_generic_types(t: Type) -> Type:
"""Remove generic type arguments and type variables from a type.
Expand Down
9 changes: 8 additions & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from mypy.types import (
Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType,
Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList,
PartialType, DeletedType
PartialType, DeletedType, TypeType
)


Expand Down Expand Up @@ -94,6 +94,13 @@ def visit_union_type(self, t: UnionType) -> Type:
def visit_partial_type(self, t: PartialType) -> Type:
return t

def visit_type_type(self, t: TypeType) -> Type:
# TODO: Verify that the new item type is valid (instance or
# union of instances or Any). Sadly we can't report errors
# here yet.
item = t.item.accept(self)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suspect that we should verify here that the new item type is valid (instance or union of instances, or Any). However, expand_type currently can't report an error, which is unfortunate. You could just write a TODO comment about this and not worry about it for now.

return TypeType(item)

def expand_types(self, types: List[Type]) -> List[Type]:
a = [] # type: List[Type]
for t in types:
Expand Down
Loading