diff --git a/lib-typing/2.7/test_typing.py b/lib-typing/2.7/test_typing.py index c11ba2210485..b801383cd646 100644 --- a/lib-typing/2.7/test_typing.py +++ b/lib-typing/2.7/test_typing.py @@ -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 @@ -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): diff --git a/lib-typing/2.7/typing.py b/lib-typing/2.7/typing.py index 44eb9f2efc68..adbf0389b855 100644 --- a/lib-typing/2.7/typing.py +++ b/lib-typing/2.7/typing.py @@ -20,9 +20,10 @@ 'Callable', 'Generic', 'Optional', + 'Tuple', + 'Type', 'TypeVar', 'Union', - 'Tuple', # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. @@ -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. @@ -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) @@ -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. diff --git a/lib-typing/3.2/test_typing.py b/lib-typing/3.2/test_typing.py index f9e54b2725fe..ade8a3587de4 100644 --- a/lib-typing/3.2/test_typing.py +++ b/lib-typing/3.2/test_typing.py @@ -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 @@ -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): diff --git a/lib-typing/3.2/typing.py b/lib-typing/3.2/typing.py index 841e778648a6..b7f3ffa0aa26 100644 --- a/lib-typing/3.2/typing.py +++ b/lib-typing/3.2/typing.py @@ -19,9 +19,10 @@ 'Callable', 'Generic', 'Optional', + 'Tuple', + 'Type', 'TypeVar', 'Union', - 'Tuple', # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. @@ -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. @@ -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) @@ -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. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d00acec622a7..64263ce6cfb2 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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, @@ -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) + if isinstance(callee, CallableType): + 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. diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f6019e3a6721..e3a692c4e97b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -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 @@ -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) diff --git a/mypy/constraints.py b/mypy/constraints.py index 4ea64f43bb65..8e5fdee06b70 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -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 @@ -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.""" diff --git a/mypy/erasetype.py b/mypy/erasetype.py index f76533e2bafd..e805f8563b0f 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -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 ) @@ -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()) @@ -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. diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 60730b563bd6..e25190fe4329 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -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 ) @@ -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) + return TypeType(item) + def expand_types(self, types: List[Type]) -> List[Type]: a = [] # type: List[Type] for t in types: diff --git a/mypy/fixup.py b/mypy/fixup.py index c661b8a7a450..73fb22775d3d 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -7,7 +7,8 @@ TypeVarExpr, ClassDef, LDEF, MDEF, GDEF, MODULE_REF) from mypy.types import (CallableType, EllipsisType, Instance, Overloaded, TupleType, - TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor) + TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor, + TypeType) from mypy.visitor import NodeVisitor @@ -214,6 +215,9 @@ def visit_union_type(self, ut: UnionType) -> None: def visit_void(self, o: Any) -> None: pass # Nothing to descend into. + def visit_type_type(self, t: TypeType) -> None: + t.item.accept(self) + def lookup_qualified(modules: Dict[str, MypyFile], name: str) -> SymbolNode: stnode = lookup_qualified_stnode(modules, name) diff --git a/mypy/join.py b/mypy/join.py index 2ae03602120b..0c42b64d7d62 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -5,7 +5,8 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList, - UnionType, FunctionLike, Overloaded, PartialType, DeletedType + UnionType, FunctionLike, Overloaded, PartialType, DeletedType, + TypeType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars @@ -130,6 +131,8 @@ def visit_instance(self, t: Instance) -> Type: return join_instances(t, self.s) elif isinstance(self.s, FunctionLike): return join_types(t, self.s.fallback) + elif isinstance(self.s, TypeType): + return join_types(t, self.s) else: return self.default(self.s) @@ -204,6 +207,14 @@ def visit_partial_type(self, t: PartialType) -> Type: # never get here. assert False, "Internal error" + def visit_type_type(self, t: TypeType) -> Type: + if isinstance(self.s, TypeType): + return TypeType(self.join(t.item, self.s.item), line=t.line) + elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': + return self.s + else: + return self.default(self.s) + def join(self, s: Type, t: Type) -> Type: return join_types(s, t) diff --git a/mypy/meet.py b/mypy/meet.py index cbffed5b24a9..eff2eedf8fab 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,7 +3,8 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType, - Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType + Instance, CallableType, TupleType, ErasedType, TypeList, + UnionType, PartialType, DeletedType, TypeType ) from mypy.subtypes import is_subtype from mypy.nodes import TypeInfo @@ -184,6 +185,8 @@ def visit_instance(self, t: Instance) -> Type: return self.s else: return NoneTyp() + elif isinstance(self.s, TypeType): + return meet_types(t, self.s) else: return self.default(self.s) @@ -207,6 +210,17 @@ def visit_partial_type(self, t: PartialType) -> Type: # We can't determine the meet of partial types. We should never get here. assert False, 'Internal error' + def visit_type_type(self, t: TypeType) -> Type: + if isinstance(self.s, TypeType): + typ = self.meet(t.item, self.s.item) + if not isinstance(typ, NoneTyp): + typ = TypeType(typ, line=t.line) + return typ + elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': + return t + else: + return self.default(self.s) + def meet(self, s, t): return meet_types(s, t) diff --git a/mypy/messages.py b/mypy/messages.py index 3d7d559a8d5d..2946910696f7 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -11,7 +11,7 @@ from mypy.errors import Errors from mypy.types import ( Type, CallableType, Instance, TypeVarType, TupleType, UnionType, Void, NoneTyp, AnyType, - Overloaded, FunctionLike, DeletedType + Overloaded, FunctionLike, DeletedType, TypeType ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, @@ -265,6 +265,9 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: return '"Any"' elif isinstance(typ, DeletedType): return '' + elif isinstance(typ, TypeType): + return 'Type[{}]'.format( + strip_quotes(self.format_simple(typ.item, verbosity))) elif typ is None: raise RuntimeError('Type is None') else: @@ -828,6 +831,9 @@ def invalid_signature(self, func_type: Type, context: Context) -> None: def reveal_type(self, typ: Type, context: Context) -> None: self.fail('Revealed type is \'{}\''.format(typ), context) + def unsupported_type_type(self, item: Type, context: Context) -> None: + self.fail('Unsupported type Type[{}]'.format(self.format(item)), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/mypy/sametypes.py b/mypy/sametypes.py index bfe37a6a1394..319af509e70c 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -2,7 +2,8 @@ from mypy.types import ( Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType, - TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType + TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, + Overloaded, PartialType, DeletedType, TypeType ) @@ -121,3 +122,9 @@ def visit_partial_type(self, left: PartialType) -> bool: # A partial type is not fully defined, so the result is indeterminate. We shouldn't # get here. raise RuntimeError + + def visit_type_type(self, left: TypeType) -> bool: + if isinstance(self.right, TypeType): + return is_same_type(left.item, self.right.item) + else: + return False diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 46db522173d9..ea47c3f67b5d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList, - PartialType, DeletedType, is_named_instance + PartialType, DeletedType, TypeType, is_named_instance ) import mypy.applytype import mypy.constraints @@ -145,6 +145,9 @@ def visit_callable_type(self, left: CallableType) -> bool: for item in right.items()) elif isinstance(right, Instance): return is_subtype(left.fallback, right) + elif isinstance(right, TypeType): + # This is unsound, we don't check the __init__ signature. + return left.is_type_obj() and is_subtype(left.ret_type, right.item) else: return False @@ -198,6 +201,11 @@ def visit_overloaded(self, left: Overloaded) -> bool: return True elif isinstance(right, UnboundType): return True + elif isinstance(right, TypeType): + # All the items must have the same type object status, so + # it's sufficient to query only (any) one of them. + # This is unsound, we don't check the __init__ signature. + return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item) else: return False @@ -209,6 +217,20 @@ def visit_partial_type(self, left: PartialType) -> bool: # This is indeterminate as we don't really know the complete type yet. raise RuntimeError + def visit_type_type(self, left: TypeType) -> bool: + right = self.right + if isinstance(right, TypeType): + return is_subtype(left.item, right.item) + if isinstance(right, CallableType): + # This is unsound, we don't check the __init__ signature. + return right.is_type_obj() and is_subtype(left.item, right.ret_type) + if (isinstance(right, Instance) and + right.type.fullname() in ('builtins.type', 'builtins.object')): + # Treat builtins.type the same as Type[Any]; + # treat builtins.object the same as Any. + return True + return False + def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False) -> bool: diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index d60c24d38c46..bf501819a3c6 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1418,3 +1418,308 @@ C(0) C(arg=0) [out] + +-- Type[C] +-- ------- + + +[case testTypeUsingTypeCBasic] +from typing import Type +class User: pass +class ProUser(User): pass +def new_user(user_class: Type[User]) -> User: + return user_class() +reveal_type(new_user(User)) # E: Revealed type is '__main__.User' +reveal_type(new_user(ProUser)) # E: Revealed type is '__main__.User' +[out] + +[case testTypeUsingTypeCDefaultInit] +from typing import Type +class B: + pass +def f(A: Type[B]) -> None: + A(0) # E: Too many arguments for "B" + A() +[out] +main: note: In function "f": + +[case testTypeUsingTypeCInitWithArg] +from typing import Type +class B: + def __init__(self, a: int) -> None: pass +def f(A: Type[B]) -> None: + A(0) + A() # E: Too few arguments for "B" +[out] +main: note: In function "f": + +[case testTypeUsingTypeCTypeVar] +from typing import Type, TypeVar +class User: pass +class ProUser(User): pass +U = TypeVar('U', bound=User) +def new_user(user_class: Type[U]) -> U: + user = user_class() + reveal_type(user) + return user +pro_user = new_user(ProUser) +reveal_type(pro_user) +[out] +main: note: In function "new_user": +main:7: error: Revealed type is 'U`-1' +main: note: At top level: +main:10: error: Revealed type is '__main__.ProUser*' + +[case testTypeUsingTypeCTypeVarDefaultInit] +from typing import Type, TypeVar +class B: + pass +T = TypeVar('T', bound=B) +def f(A: Type[T]) -> None: + A() + A(0) # E: Too many arguments for "B" +[out] +main: note: In function "f": + +[case testTypeUsingTypeCTypeVarWithInit] +from typing import Type, TypeVar +class B: + def __init__(self, a: int) -> None: pass +T = TypeVar('T', bound=B) +def f(A: Type[T]) -> None: + A() # E: Too few arguments for "B" + A(0) +[out] +main: note: In function "f": + +[case testTypeUsingTypeCTwoTypeVars] +from typing import Type, TypeVar +class User: pass +class ProUser(User): pass +class WizUser(ProUser): pass +U = TypeVar('U', bound=User) +def new_user(u_c: Type[U]) -> U: pass +P = TypeVar('P', bound=ProUser) +def new_pro(pro_c: Type[P]) -> P: + return new_user(pro_c) +wiz = new_pro(WizUser) +reveal_type(wiz) +def error(u_c: Type[U]) -> P: + return new_pro(u_c) # Error here, see below +[out] +main:11: error: Revealed type is '__main__.WizUser*' +main: note: In function "error": +main:13: error: Type argument 1 of "new_pro" has incompatible value "U" +main:13: error: Incompatible return value type (got "U", expected "P") + +[case testTypeUsingTypeCCovariance] +from typing import Type, TypeVar +class User: pass +class ProUser(User): pass +def new_user(user_class: Type[User]) -> User: + return user_class() +def new_pro_user(user_class: Type[ProUser]): + new_user(user_class) +[out] + +[case testTypeUsingTypeCErrorCovariance] +from typing import Type, TypeVar +class User: pass +def new_user(user_class: Type[User]): + return user_class() +def foo(arg: Type[int]): + new_user(arg) # E: Argument 1 to "new_user" has incompatible type Type[int]; expected Type[User] +[out] +main: note: In function "foo": + +[case testTypeUsingTypeCUnionOverload] +from typing import Type, Union, overload +class X: + @overload + def __init__(self) -> None: pass + @overload + def __init__(self, a: int) -> None: pass +class Y: + def __init__(self) -> None: pass +def bar(o: Type[Union[X, Y]]): pass +bar(X) +bar(Y) +[out] + +[case testTypeUsingTypeCTypeAny] +from typing import Type, Any +def foo(arg: Type[Any]): + x = arg() + x = arg(0) + x = arg('', ()) + reveal_type(x) # E: Revealed type is 'Any' + x.foo +class X: pass +foo(X) +[out] +main: note: In function "foo": + +[case testTypeUsingTypeCTypeNoArg] +from typing import Type +def foo(arg: Type): + x = arg() + reveal_type(x) # E: Revealed type is 'Any' +class X: pass +foo(X) +[out] +main: note: In function "foo": + +[case testTypeUsingTypeCBuiltinType] +from typing import Type +def foo(arg: type): pass +class X: pass +def bar(arg: Type[X]): + foo(arg) +foo(X) +[builtins fixtures/tuple.py] +[out] + +[case testTypeUsingTypeCClassMethod] +from typing import Type +class User: + @classmethod + def foo(cls) -> int: pass + def bar(self) -> int: pass +def process(cls: Type[User]): + reveal_type(cls.foo()) # E: Revealed type is 'builtins.int' + obj = cls() + reveal_type(cls.bar(obj)) # E: Revealed type is 'builtins.int' + cls.mro() # Defined in class type + cls.error # E: Type[User] has no attribute "error" +[builtins fixtures/classmethod.py] +[out] +main: note: In function "process": + +[case testTypeUsingTypeCClassMethodUnion] +# Ideally this would work, but not worth the effort; just don't crash +from typing import Type, Union +class User: + @classmethod + def foo(cls) -> int: pass + def bar(self) -> int: pass +class ProUser(User): pass +class BasicUser(User): pass +def process(cls: Type[Union[BasicUser, ProUser]]): + cls.foo() # E: Type[Union[BasicUser, ProUser]] has no attribute "foo" + obj = cls() + cls.bar(obj) # E: Type[Union[BasicUser, ProUser]] has no attribute "bar" + cls.mro() # Defined in class type + cls.error # E: Type[Union[BasicUser, ProUser]] has no attribute "error" +[builtins fixtures/classmethod.py] +[out] +main: note: In function "process": + +[case testTypeUsingTypeCClassMethodFromTypeVar] +from typing import Type, TypeVar +class User: + @classmethod + def foo(cls) -> int: pass + def bar(self) -> int: pass +U = TypeVar('U', bound=User) +def process(cls: Type[U]): + reveal_type(cls.foo()) # E: Revealed type is 'builtins.int' + obj = cls() + reveal_type(cls.bar(obj)) # E: Revealed type is 'builtins.int' + cls.mro() # Defined in class type + cls.error # E: Type[U] has no attribute "error" +[builtins fixtures/classmethod.py] +[out] +main: note: In function "process": + +[case testTypeUsingTypeCClassMethodFromTypeVarUnionBound] +# Ideally this would work, but not worth the effort; just don't crash +from typing import Type, TypeVar, Union +class User: + @classmethod + def foo(cls) -> int: pass + def bar(self) -> int: pass +class ProUser(User): pass +class BasicUser(User): pass +U = TypeVar('U', bound=Union[ProUser, BasicUser]) +def process(cls: Type[U]): + cls.foo() # E: Type[U] has no attribute "foo" + obj = cls() + cls.bar(obj) # E: Type[U] has no attribute "bar" + cls.mro() # Defined in class type + cls.error # E: Type[U] has no attribute "error" +[builtins fixtures/classmethod.py] +[out] +main: note: In function "process": + +[case testTypeUsingTypeCErrorUnsupportedType] +from typing import Type, Tuple +def foo(arg: Type[Tuple[int]]): # E: Unsupported type Type["Tuple[int]"] + arg() +[builtins fixtures/tuple.py] +[out] +main: note: In function "foo": + +[case testTypeUsingTypeCOverloadedClass] +from typing import Type, TypeVar, overload +class User: + @overload + def __init__(self) -> None: pass + @overload + def __init__(self, arg: int) -> None: pass + @classmethod + def foo(cls) -> None: pass +U = TypeVar('U', bound=User) +def new(uc: Type[U]) -> U: + uc.foo() + u = uc() + u.foo() + u = uc(0) + u.foo() + u = uc('') + u.foo(0) + return uc() +u = new(User) +[builtins fixtures/classmethod.py] +[out] +main: note: In function "new": +main:16: error: No overload variant of "User" matches argument types [builtins.str] +main:17: error: Too many arguments for "foo" of "User" + +[case testTypeUsingTypeCInUpperBound] +from typing import TypeVar, Type +class B: pass +T = TypeVar('T', bound=Type[B]) +def f(a: T): pass +[out] + +[case testTypeUsingTypeCTuple] +from typing import Type, Tuple +def f(a: Type[Tuple[int, int]]): + a() +[out] +main: note: In function "f": +main:2: error: Unsupported type Type["Tuple[int, int]"] + +[case testTypeUsingTypeCNamedTuple] +from typing import Type, NamedTuple +N = NamedTuple('N', [('x', int), ('y', int)]) +def f(a: Type[N]): + a() +[builtins fixtures/list.py] +[out] +main: note: In function "f": +main:3: error: Unsupported type Type["N"] + +[case testTypeUsingTypeCJoin] +from typing import Type +class B: pass +class C(B): pass +class D(B): pass +def foo(c: Type[C], d: Type[D]) -> None: + x = [c, d] + reveal_type(x) + +[builtins fixtures/list.py] +[out] +main: note: In function "foo": +main:7: error: Revealed type is 'builtins.list[Type[__main__.B]]' diff --git a/mypy/test/data/fixtures/classmethod.py b/mypy/test/data/fixtures/classmethod.py index 634e1e0c30e8..98cff8a15e2a 100644 --- a/mypy/test/data/fixtures/classmethod.py +++ b/mypy/test/data/fixtures/classmethod.py @@ -5,6 +5,7 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass + def mro(self) -> typing.Any: pass class function: pass diff --git a/mypy/test/data/fixtures/list.py b/mypy/test/data/fixtures/list.py index a50fa7ce6dfb..55c8999cb46a 100644 --- a/mypy/test/data/fixtures/list.py +++ b/mypy/test/data/fixtures/list.py @@ -14,7 +14,7 @@ class list(Iterable[T], Generic[T]): @overload def __init__(self) -> None: pass @overload - def __init__(self, x: Iterator[T]) -> None: pass + def __init__(self, x: Iterable[T]) -> None: pass def __iter__(self) -> Iterator[T]: pass def __mul__(self, x: int) -> list[T]: pass def __getitem__(self, x: int) -> T: pass diff --git a/mypy/test/data/fixtures/tuple.py b/mypy/test/data/fixtures/tuple.py index 158a812cd7ea..76c109127364 100644 --- a/mypy/test/data/fixtures/tuple.py +++ b/mypy/test/data/fixtures/tuple.py @@ -7,7 +7,9 @@ class object: def __init__(self): pass -class type: pass +class type: + def __init__(self, *a) -> None: pass + def __call__(self, *a) -> object: pass class tuple(Sequence[Tco], Generic[Tco]): def __getitem__(self, x: int) -> Tco: pass class function: pass diff --git a/mypy/test/data/lib-stub/typing.py b/mypy/test/data/lib-stub/typing.py index 07cd6f75010c..09c76a7eb1bf 100644 --- a/mypy/test/data/lib-stub/typing.py +++ b/mypy/test/data/lib-stub/typing.py @@ -15,6 +15,7 @@ builtinclass = 0 _promote = 0 NamedTuple = 0 +Type = 0 no_type_check = 0 # Type aliases. diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 754ed7271a71..e4b8c0294900 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -11,7 +11,7 @@ from mypy.meet import meet_types from mypy.types import ( UnboundType, AnyType, Void, CallableType, TupleType, TypeVarDef, Type, - Instance, NoneTyp, ErrorType, Overloaded + Instance, NoneTyp, ErrorType, Overloaded, TypeType, ) from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, CONTRAVARIANT, INVARIANT, COVARIANT from mypy.replacetvars import replace_type_vars @@ -177,6 +177,10 @@ def test_erase_with_type_object(self): self.assert_erase(self.fx.callable_type(self.fx.a, self.fx.b), self.fx.callable_type(self.fx.void)) + def test_erase_with_type_type(self): + self.assert_erase(self.fx.type_a, self.fx.type_a) + self.assert_erase(self.fx.type_t, self.fx.type_any) + def assert_erase(self, orig, result): assert_equal(str(erase_type(orig)), str(result)) @@ -491,6 +495,15 @@ def test_simple_type_objects(self): self.assert_join(self.fx.type_type, self.fx.type_type, self.fx.type_type) + def test_type_type(self): + self.assert_join(self.fx.type_a, self.fx.type_b, self.fx.type_a) + self.assert_join(self.fx.type_b, self.fx.type_any, self.fx.type_any) + self.assert_join(self.fx.type_b, self.fx.type_type, self.fx.type_type) + self.assert_join(self.fx.type_b, self.fx.type_c, self.fx.type_a) + self.assert_join(self.fx.type_c, self.fx.type_d, TypeType(self.fx.o)) + self.assert_join(self.fx.type_type, self.fx.type_any, self.fx.type_type) + self.assert_join(self.fx.type_b, self.fx.anyt, self.fx.anyt) + # There are additional test cases in check-inference.test. # TODO: Function types + varargs and default args. @@ -678,13 +691,13 @@ def test_meet_interface_types(self): self.assert_meet(self.fx.f, self.fx.f2, self.fx.nonet) self.assert_meet(self.fx.f, self.fx.f3, self.fx.f3) - def test_join_interface_and_class_types(self): + def test_meet_interface_and_class_types(self): self.assert_meet(self.fx.o, self.fx.f, self.fx.f) self.assert_meet(self.fx.a, self.fx.f, self.fx.nonet) self.assert_meet(self.fx.e, self.fx.f, self.fx.e) - def test_join_class_types_with_shared_interfaces(self): + def test_meet_class_types_with_shared_interfaces(self): # These have nothing special with respect to meets, unlike joins. These # are for completeness only. self.assert_meet(self.fx.e, self.fx.e2, self.fx.nonet) @@ -699,6 +712,15 @@ def test_meet_with_generic_interfaces(self): self.assert_meet(fx.gfa, fx.gfa, fx.gfa) self.assert_meet(fx.gfb, fx.m1, fx.nonet) + def test_type_type(self): + self.assert_meet(self.fx.type_a, self.fx.type_b, self.fx.type_b) + self.assert_meet(self.fx.type_b, self.fx.type_any, self.fx.type_b) + self.assert_meet(self.fx.type_b, self.fx.type_type, self.fx.type_b) + self.assert_meet(self.fx.type_b, self.fx.type_c, self.fx.nonet) + self.assert_meet(self.fx.type_c, self.fx.type_d, self.fx.nonet) + self.assert_meet(self.fx.type_type, self.fx.type_any, self.fx.type_any) + self.assert_meet(self.fx.type_b, self.fx.anyt, self.fx.type_b) + # FIX generic interfaces + ranges def assert_meet(self, s, t, meet): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 659277fa7609..6a7ef29cf9b1 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, StarType, PartialType, - EllipsisType + EllipsisType, TypeType ) from mypy.nodes import ( BOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -18,7 +18,7 @@ from mypy import nodes -type_constructors = ['typing.Tuple', 'typing.Union', 'typing.Callable'] +type_constructors = {'typing.Tuple', 'typing.Union', 'typing.Callable', 'typing.Type'} def analyze_type_alias(node: Node, @@ -114,6 +114,14 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return items[0] elif fullname == 'typing.Callable': return self.analyze_callable_type(t) + elif fullname == 'typing.Type': + if len(t.args) == 0: + return TypeType(AnyType(), line=t.line) + if len(t.args) != 1: + self.fail('Type[...] must have exactly one type argument', t) + items = self.anal_array(t.args) + item = items[0] + return TypeType(item, line=t.line) elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. return sym.type_override @@ -207,6 +215,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: self.fail("Unexpected '...'", t) return AnyType() + def visit_type_type(self, t: TypeType) -> Type: + return TypeType(t.item.accept(self), line=t.line) + def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') if len(t.args) == 0: @@ -384,3 +395,6 @@ def visit_type_var(self, t: TypeVarType) -> None: def visit_partial_type(self, t: PartialType) -> None: pass + + def visit_type_type(self, t: TypeType) -> None: + pass diff --git a/mypy/typefixture.py b/mypy/typefixture.py index ffd1e84d90bd..ec76cf80d68b 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -6,7 +6,8 @@ from typing import List from mypy.types import ( - TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, TypeVarDef + TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, TypeVarDef, + TypeType, ) from mypy.nodes import ( TypeInfo, ClassDef, Block, ARG_POS, ARG_OPT, ARG_STAR, SymbolTable, @@ -140,6 +141,13 @@ def __init__(self, variance: int=COVARIANT) -> None: self.lsta = Instance(self.std_listi, [self.a]) # List[A] self.lstb = Instance(self.std_listi, [self.b]) # List[B] + self.type_a = TypeType(self.a) + self.type_b = TypeType(self.b) + self.type_c = TypeType(self.c) + self.type_d = TypeType(self.d) + self.type_t = TypeType(self.t) + self.type_any = TypeType(self.anyt) + # Helper methods def callable(self, *a): diff --git a/mypy/types.py b/mypy/types.py index 1d2c231abc6e..92b86593c992 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -783,6 +783,53 @@ def deserialize(self, data: JsonDict) -> 'EllipsisType': return EllipsisType() +class TypeType(Type): + """For types like Type[User]. + + This annotates variables that are class objects, constrained by + the type argument. See PEP 484 for more details. + + We may encounter expressions whose values are specific classes; + those are represented as callables (possibly overloaded) + corresponding to the class's constructor's signature and returning + an instance of that class. The difference with Type[C] is that + those callables always represent the exact class given as the + return type; Type[C] represents any class that's a subclass of C, + and C may also be a type variable or a union (or Any). + + Many questions around subtype relationships between Type[C1] and + def(...) -> C2 are answered by looking at the subtype + relationships between C1 and C2, since Type[] is considered + covariant. + + There's an unsolved problem with constructor signatures (also + unsolved in PEP 484): calling a variable whose type is Type[C] + assumes the constructor signature for C, even though a subclass of + C might completely change the constructor signature. For now we + just assume that users of Type[C] are careful not to do that (in + the future we might detect when they are violating that + assumption). + """ + + # This can't be everything, but it can be a class reference, + # a generic class instance, a union, Any, a type variable... + item = None # type: Type + + def __init__(self, item: Type, *, line: int = -1) -> None: + super().__init__(line) + self.item = item + + def accept(self, visitor: 'TypeVisitor[T]') -> T: + return visitor.visit_type_type(self) + + def serialize(self) -> JsonDict: + return {'.class': 'TypeType', 'item': self.item.serialize()} + + def deserialize(self, data: JsonDict) -> 'TypeType': + assert data['.class'] == 'TypeType' + return TypeType(Type.deserialize(data['item'])) + + # # Visitor-related classes # @@ -794,9 +841,9 @@ class TypeVisitor(Generic[T]): The parameter T is the return type of the visit methods. """ - def _notimplemented_helper(self) -> NotImplementedError: - return NotImplementedError("Method visit_type_list not implemented in " - + "'{}'\n".format(type(self).__name__) + def _notimplemented_helper(self, name: str) -> NotImplementedError: + return NotImplementedError("Method {}.visit_{}() not implemented\n" + .format(type(self).__name__, name) + "This is a known bug, track development in " + "'https://github.com/JukkaL/mypy/issues/730'") @@ -805,10 +852,10 @@ def visit_unbound_type(self, t: UnboundType) -> T: pass def visit_type_list(self, t: TypeList) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('type_list') def visit_error_type(self, t: ErrorType) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('error_type') @abstractmethod def visit_any(self, t: AnyType) -> T: @@ -823,7 +870,7 @@ def visit_none_type(self, t: NoneTyp) -> T: pass def visit_erased_type(self, t: ErasedType) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('erased_type') @abstractmethod def visit_deleted_type(self, t: DeletedType) -> T: @@ -842,14 +889,14 @@ def visit_callable_type(self, t: CallableType) -> T: pass def visit_overloaded(self, t: Overloaded) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('overloaded') @abstractmethod def visit_tuple_type(self, t: TupleType) -> T: pass def visit_star_type(self, t: StarType) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('star_type') @abstractmethod def visit_union_type(self, t: UnionType) -> T: @@ -860,7 +907,11 @@ def visit_partial_type(self, t: PartialType) -> T: pass def visit_ellipsis_type(self, t: EllipsisType) -> T: - raise self._notimplemented_helper() + raise self._notimplemented_helper('ellipsis_type') + + @abstractmethod + def visit_type_type(self, t: TypeType) -> T: + pass class TypeTranslator(TypeVisitor[Type]): @@ -939,6 +990,9 @@ def visit_overloaded(self, t: Overloaded) -> Type: raise RuntimeError('CallableType expectected, but got {}'.format(type(new))) return Overloaded(items=items) + def visit_type_type(self, t: TypeType) -> Type: + return TypeType(t.item.accept(self), line=t.line) + class TypeStrVisitor(TypeVisitor[str]): """Visitor for pretty-printing types into strings. @@ -1060,6 +1114,9 @@ def visit_partial_type(self, t: PartialType) -> str: def visit_ellipsis_type(self, t): return '...' + def visit_type_type(self, t): + return 'Type[{}]'.format(t.item.accept(self)) + def list_str(self, a): """Convert items of an array to strings (pretty-print types) and join the results with commas. @@ -1149,6 +1206,9 @@ def visit_union_type(self, t: UnionType) -> bool: def visit_overloaded(self, t: Overloaded) -> bool: return self.query_types(t.items()) + def visit_type_type(self, t: TypeType) -> bool: + return t.item.accept(self) + def query_types(self, types: Sequence[Type]) -> bool: """Perform a query for a list of types.