Skip to content

Commit 8555adb

Browse files
gvanrossumJukkaL
authored andcommitted
Type[C] (#1569)
Preserved original commit log (only slightly cleaned up): * Tentative tests for Type[C]. * Pave the way for implementing Type[C]. * Introducing TypeType: Type[x] becomes TypeType(x). * Fix/new tests; implement more subtype checks. * Towards supporting Type[T]. * Fix things so all tests pass! * Some cleanup and fixup (not done). - Add tests for Type[Any] and plain Type, and fix a bug that found. - Respond to easy review feedback (adding comments/docstrings mostly). * Improve equivalence between type and Type. * Add support for method lookup from Type[]. We only support two special cases (hopefully the most common ones): - Type[<concrete_class>] - Type[<type_variable_with_concrete_class_upper_bound>] * Print a proper error for unsupported Type[] args. (This happens when type checking a call, not during semantic analysis.) * Reject Type[U] where U's bound is a generic class. * Support classes with @overload-ed __init__. * Update our copies of typing.py to the latest from the python/typing repo. * Make Type[A] erase to Type[A], but Type[T] erases to Type[Any]. * Special-case joining Type[] with builtins.type. Also add tests. * Special-case joining Type[] with builtins.type. Also add tests. * Delete outdated XXX comment. * Finishing touch -- fix and test TypeAnalyser.visit_type_type(). * Added tests as requested. * Remove comment asking for name for analyze_type_type_callee(). * Use self.default(self.s) instead of AnyType()/NonType() in join/meet. * Add tests for overloaded __init__. * Add more tests. Fixed a bug in join() this found.
1 parent 3c3e563 commit 8555adb

24 files changed

+715
-32
lines changed

lib-typing/2.7/test_typing.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from typing import Callable
1515
from typing import Generic
1616
from typing import cast
17+
from typing import Type
1718
from typing import NamedTuple
1819
from typing import IO, TextIO, BinaryIO
1920
from typing import Pattern, Match
@@ -1110,6 +1111,36 @@ def __len__(self):
11101111
self.assertIsSubclass(MMC, typing.Mapping)
11111112

11121113

1114+
class TypeTests(BaseTestCase):
1115+
1116+
def test_type_basic(self):
1117+
1118+
class User(object): pass
1119+
class BasicUser(User): pass
1120+
class ProUser(User): pass
1121+
1122+
def new_user(user_class):
1123+
# type: (Type[User]) -> User
1124+
return user_class()
1125+
1126+
joe = new_user(BasicUser)
1127+
1128+
def test_type_typevar(self):
1129+
1130+
class User(object): pass
1131+
class BasicUser(User): pass
1132+
class ProUser(User): pass
1133+
1134+
global U
1135+
U = TypeVar('U', bound=User)
1136+
1137+
def new_user(user_class):
1138+
# type: (Type[U]) -> U
1139+
return user_class()
1140+
1141+
joe = new_user(BasicUser)
1142+
1143+
11131144
class NamedTupleTests(BaseTestCase):
11141145

11151146
def test_basics(self):

lib-typing/2.7/typing.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
'Callable',
2121
'Generic',
2222
'Optional',
23+
'Tuple',
24+
'Type',
2325
'TypeVar',
2426
'Union',
25-
'Tuple',
2627

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

455456

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

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

468471

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

15201523

1524+
# Internal type variable used for Type[].
1525+
CT = TypeVar('CT', covariant=True, bound=type)
1526+
1527+
1528+
# This is not a real generic class. Don't use outside annotations.
1529+
class Type(type, Generic[CT]):
1530+
"""A special construct usable to annotate class objects.
1531+
1532+
For example, suppose we have the following classes::
1533+
1534+
class User: ... # Abstract base for User classes
1535+
class BasicUser(User): ...
1536+
class ProUser(User): ...
1537+
class TeamUser(User): ...
1538+
1539+
And a function that takes a class argument that's a subclass of
1540+
User and returns an instance of the corresponding class::
1541+
1542+
U = TypeVar('U', bound=User)
1543+
def new_user(user_class: Type[U]) -> U:
1544+
user = user_class()
1545+
# (Here we could write the user object to a database)
1546+
return user
1547+
1548+
joe = new_user(BasicUser)
1549+
1550+
At this point the type checker knows that joe has type BasicUser.
1551+
"""
1552+
__extra__ = type
1553+
1554+
15211555
def NamedTuple(typename, fields):
15221556
"""Typed version of namedtuple.
15231557

lib-typing/3.2/test_typing.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import cast
1616
from typing import get_type_hints
1717
from typing import no_type_check, no_type_check_decorator
18+
from typing import Type
1819
from typing import NamedTuple
1920
from typing import IO, TextIO, BinaryIO
2021
from typing import Pattern, Match
@@ -1373,6 +1374,33 @@ def manager():
13731374
self.assertNotIsInstance(42, typing.ContextManager)
13741375

13751376

1377+
class TypeTests(BaseTestCase):
1378+
1379+
def test_type_basic(self):
1380+
1381+
class User: pass
1382+
class BasicUser(User): pass
1383+
class ProUser(User): pass
1384+
1385+
def new_user(user_class: Type[User]) -> User:
1386+
return user_class()
1387+
1388+
joe = new_user(BasicUser)
1389+
1390+
def test_type_typevar(self):
1391+
1392+
class User: pass
1393+
class BasicUser(User): pass
1394+
class ProUser(User): pass
1395+
1396+
U = TypeVar('U', bound=User)
1397+
1398+
def new_user(user_class: Type[U]) -> U:
1399+
return user_class()
1400+
1401+
joe = new_user(BasicUser)
1402+
1403+
13761404
class NamedTupleTests(BaseTestCase):
13771405

13781406
def test_basics(self):

lib-typing/3.2/typing.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
'Callable',
2020
'Generic',
2121
'Optional',
22+
'Tuple',
23+
'Type',
2224
'TypeVar',
2325
'Union',
24-
'Tuple',
2526

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

448449

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

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

461464

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

15741577

1578+
# Internal type variable used for Type[].
1579+
CT = TypeVar('CT', covariant=True, bound=type)
1580+
1581+
1582+
# This is not a real generic class. Don't use outside annotations.
1583+
class Type(type, Generic[CT], extra=type):
1584+
"""A special construct usable to annotate class objects.
1585+
1586+
For example, suppose we have the following classes::
1587+
1588+
class User: ... # Abstract base for User classes
1589+
class BasicUser(User): ...
1590+
class ProUser(User): ...
1591+
class TeamUser(User): ...
1592+
1593+
And a function that takes a class argument that's a subclass of
1594+
User and returns an instance of the corresponding class::
1595+
1596+
U = TypeVar('U', bound=User)
1597+
def new_user(user_class: Type[U]) -> U:
1598+
user = user_class()
1599+
# (Here we could write the user object to a database)
1600+
return user
1601+
1602+
joe = new_user(BasicUser)
1603+
1604+
At this point the type checker knows that joe has type BasicUser.
1605+
"""
1606+
1607+
15751608
def NamedTuple(typename, fields):
15761609
"""Typed version of namedtuple.
15771610

mypy/checkexpr.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.types import (
66
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
77
TupleType, Instance, TypeVarType, ErasedType, UnionType,
8-
PartialType, DeletedType, UnboundType
8+
PartialType, DeletedType, UnboundType, TypeType
99
)
1010
from mypy.nodes import (
1111
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
@@ -277,9 +277,49 @@ def check_call(self, callee: Type, args: List[Node],
277277
elif isinstance(callee, TypeVarType):
278278
return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names,
279279
callable_node, arg_messages)
280+
elif isinstance(callee, TypeType):
281+
# Pass the original Type[] as context since that's where errors should go.
282+
item = self.analyze_type_type_callee(callee.item, callee)
283+
return self.check_call(item, args, arg_kinds, context, arg_names,
284+
callable_node, arg_messages)
280285
else:
281286
return self.msg.not_callable(callee, context), AnyType()
282287

288+
def analyze_type_type_callee(self, item: Type, context: Context) -> Type:
289+
"""Analyze the callee X in X(...) where X is Type[item].
290+
291+
Return a Y that we can pass to check_call(Y, ...).
292+
"""
293+
if isinstance(item, AnyType):
294+
return AnyType()
295+
if isinstance(item, Instance):
296+
return type_object_type(item.type, self.named_type)
297+
if isinstance(item, UnionType):
298+
return UnionType([self.analyze_type_type_callee(item, context)
299+
for item in item.items], item.line)
300+
if isinstance(item, TypeVarType):
301+
# Pretend we're calling the typevar's upper bound,
302+
# i.e. its constructor (a poor approximation for reality,
303+
# but better than AnyType...), but replace the return type
304+
# with typevar.
305+
callee = self.analyze_type_type_callee(item.upper_bound, context)
306+
if isinstance(callee, CallableType):
307+
if callee.is_generic():
308+
callee = None
309+
else:
310+
callee = callee.copy_modified(ret_type=item)
311+
elif isinstance(callee, Overloaded):
312+
if callee.items()[0].is_generic():
313+
callee = None
314+
else:
315+
callee = Overloaded([c.copy_modified(ret_type=item)
316+
for c in callee.items()])
317+
if callee:
318+
return callee
319+
320+
self.msg.unsupported_type_type(item, context)
321+
return AnyType()
322+
283323
def infer_arg_types_in_context(self, callee: CallableType,
284324
args: List[Node]) -> List[Type]:
285325
"""Infer argument expression types using a callable type as context.

mypy/checkmember.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from mypy.types import (
66
Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarDef,
7-
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType, DeletedType, NoneTyp
7+
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType,
8+
DeletedType, NoneTyp, TypeType
89
)
910
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context
1011
from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, function_type, Decorator, OverloadedFuncDef
@@ -113,6 +114,23 @@ def analyze_member_access(name: str, typ: Type, node: Context, is_lvalue: bool,
113114
elif isinstance(typ, DeletedType):
114115
msg.deleted_as_rvalue(typ, node)
115116
return AnyType()
117+
elif isinstance(typ, TypeType):
118+
# Similar to FunctionLike + is_type_obj() above.
119+
item = None
120+
if isinstance(typ.item, Instance):
121+
item = typ.item
122+
elif isinstance(typ.item, TypeVarType):
123+
if isinstance(typ.item.upper_bound, Instance):
124+
item = typ.item.upper_bound
125+
if item:
126+
result = analyze_class_attribute_access(item, name, node, is_lvalue,
127+
builtin_type, not_ready_callback, msg)
128+
if result:
129+
return result
130+
fallback = builtin_type('builtins.type')
131+
return analyze_member_access(name, fallback, node, is_lvalue, is_super,
132+
builtin_type, not_ready_callback, msg,
133+
report_type=report_type)
116134
return msg.has_no_attr(report_type, name, node)
117135

118136

mypy/constraints.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.types import (
66
CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType,
77
Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType,
8-
is_named_instance
8+
TypeType, is_named_instance
99
)
1010
from mypy.maptype import map_instance_to_supertype
1111
from mypy import nodes
@@ -348,12 +348,23 @@ def infer_against_any(self, types: List[Type]) -> List[Constraint]:
348348
res.extend(infer_constraints(t, AnyType(), self.direction))
349349
return res
350350

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

357+
def visit_type_type(self, template: TypeType) -> List[Constraint]:
358+
if isinstance(self.actual, CallableType) and self.actual.is_type_obj():
359+
return infer_constraints(template.item, self.actual.ret_type, self.direction)
360+
elif isinstance(self.actual, Overloaded) and self.actual.is_type_obj():
361+
return infer_constraints(template.item, self.actual.items()[0].ret_type,
362+
self.direction)
363+
elif isinstance(self.actual, TypeType):
364+
return infer_constraints(template.item, self.actual.item, self.direction)
365+
else:
366+
return []
367+
357368

358369
def neg_op(op: int) -> int:
359370
"""Map SubtypeOf to SupertypeOf and vice versa."""

mypy/erasetype.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.types import (
44
Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp,
55
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType,
6-
PartialType, DeletedType, TypeTranslator, TypeList
6+
PartialType, DeletedType, TypeTranslator, TypeList, TypeType
77
)
88

99

@@ -18,6 +18,7 @@ def erase_type(typ: Type) -> Type:
1818
B[X] -> B[Any]
1919
Tuple[A, B] -> tuple
2020
Callable[...] -> Callable[[], None]
21+
Type[X] -> Type[Any]
2122
"""
2223

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

76+
def visit_type_type(self, t: TypeType) -> Type:
77+
return TypeType(t.item.accept(self), line=t.line)
78+
7579

7680
def erase_generic_types(t: Type) -> Type:
7781
"""Remove generic type arguments and type variables from a type.

mypy/expandtype.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.types import (
44
Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType,
55
Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList,
6-
PartialType, DeletedType
6+
PartialType, DeletedType, TypeType
77
)
88

99

@@ -94,6 +94,13 @@ def visit_union_type(self, t: UnionType) -> Type:
9494
def visit_partial_type(self, t: PartialType) -> Type:
9595
return t
9696

97+
def visit_type_type(self, t: TypeType) -> Type:
98+
# TODO: Verify that the new item type is valid (instance or
99+
# union of instances or Any). Sadly we can't report errors
100+
# here yet.
101+
item = t.item.accept(self)
102+
return TypeType(item)
103+
97104
def expand_types(self, types: List[Type]) -> List[Type]:
98105
a = [] # type: List[Type]
99106
for t in types:

0 commit comments

Comments
 (0)