From b7ddf3617d8853a9404c32e8e24af3203ccf7377 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 18 May 2016 16:52:44 -0700 Subject: [PATCH 01/23] Tentative tests for Type[C]. --- mypy/test/data/check-classes.test | 29 +++++++++++++++++++++++++++++ mypy/test/data/lib-stub/typing.py | 3 +++ 2 files changed, 32 insertions(+) diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index d60c24d38c460..e715c11c0966a 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1418,3 +1418,32 @@ C(0) C(arg=0) [out] + +-- Type[C] +-- ------- + + +[case testTypeCBasic] +from typing import Type +class User: pass +class ProUser: pass +class BasicUser: pass +def new_user(user_class: Type[User]) -> User: + return user_class() +reveal_type(new_user(ProUser)) +[out] +main: note: At top level: +main:7: error: Revealed type is '__main__.User' + +[case testTypeCTypeVar] +from typing import Type, TypeVar +class User: pass +class ProUser: pass +class BasicUser: pass +U = TypeVar('U', bound=User) +def new_user(user_class: Type[U]) -> U: + return user_class() +reveal_type(new_user(ProUser)) +[out] +main: note: At top level: +main:8: error: Revealed type is '__main__.ProUser' diff --git a/mypy/test/data/lib-stub/typing.py b/mypy/test/data/lib-stub/typing.py index 07cd6f75010cc..e029cbcbb417f 100644 --- a/mypy/test/data/lib-stub/typing.py +++ b/mypy/test/data/lib-stub/typing.py @@ -59,3 +59,6 @@ def __iter__(self) -> 'Generator[T, U, V]': pass class Sequence(Generic[T]): @abstractmethod def __getitem__(self, n: Any) -> T: pass + +class Type(Generic[T]): + def __call__(self, *a, **k) -> T: pass From eb61f1db025b23831c85f8bb8a1b6e8314da58cd Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 20 May 2016 11:20:09 -0700 Subject: [PATCH 02/23] Pave the way for implementing Type[C]. --- mypy/test/data/check-classes.test | 4 ++-- mypy/test/data/lib-stub/typing.py | 4 +--- mypy/typeanal.py | 8 +++++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index e715c11c0966a..d22a470fb6874 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1423,7 +1423,7 @@ C(arg=0) -- ------- -[case testTypeCBasic] +[case testTypeUsingTypeCBasic] from typing import Type class User: pass class ProUser: pass @@ -1435,7 +1435,7 @@ reveal_type(new_user(ProUser)) main: note: At top level: main:7: error: Revealed type is '__main__.User' -[case testTypeCTypeVar] +[case testTypeUsingTypeCTypeVar] from typing import Type, TypeVar class User: pass class ProUser: pass diff --git a/mypy/test/data/lib-stub/typing.py b/mypy/test/data/lib-stub/typing.py index e029cbcbb417f..09c76a7eb1bf9 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. @@ -59,6 +60,3 @@ def __iter__(self) -> 'Generator[T, U, V]': pass class Sequence(Generic[T]): @abstractmethod def __getitem__(self, n: Any) -> T: pass - -class Type(Generic[T]): - def __call__(self, *a, **k) -> T: pass diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 659277fa7609f..80af64aecde73 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -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,12 @@ 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) != 1: + self.fail('Type[...] must have exactly one type argument', t) + items = self.anal_array(t.args) + # Currently Type[t] is just an alias for t. + return items[0] elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. return sym.type_override From 7b52ac6697775cedaeb056a7089cc44913972438 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 20 May 2016 15:48:50 -0700 Subject: [PATCH 03/23] WIP: Introducing TypeType: Type[x] becomes TypeType(x). --- mypy/checkexpr.py | 19 +++++++++++- mypy/fixup.py | 6 +++- mypy/subtypes.py | 5 +++- mypy/test/data/check-classes.test | 26 ++++++++++++---- mypy/typeanal.py | 12 ++++++-- mypy/types.py | 49 +++++++++++++++++++++++++------ 6 files changed, 97 insertions(+), 20 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d00acec622a76..59641d1ded4ba 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,26 @@ 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): + item = self.analyze_type_type(callee.item, context) + 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(self, arg: Type, context: Context) -> Type: + """Given the arg from (x: Type[arg]), return the type for x.""" + if isinstance(arg, AnyType): + return AnyType() + if isinstance(arg, Instance): + return type_object_type(arg.type, self.named_type) + if isinstance(arg, UnionType): + return UnionType([self.analyze_type_type(item, context) for item in arg.items], arg.line) + # XXX Do we need to handle other forms? + # XXX Make a nicely formatted error message. + self.msg.fail("XXX Bad arg to Type[]", 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/fixup.py b/mypy/fixup.py index c661b8a7a4506..73fb22775d3d1 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/subtypes.py b/mypy/subtypes.py index 46db522173d9b..59a4ce0066587 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): + # XXX Or left.is_type_obj()? + return left.is_concrete_type_obj() and is_subtype(left.ret_type, right.item) else: return False diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index d22a470fb6874..739518d1472ec 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1426,20 +1426,21 @@ C(arg=0) [case testTypeUsingTypeCBasic] from typing import Type class User: pass -class ProUser: pass -class BasicUser: pass +class ProUser(User): pass +class BasicUser(User): pass def new_user(user_class: Type[User]) -> User: return user_class() +reveal_type(new_user(User)) reveal_type(new_user(ProUser)) [out] -main: note: At top level: main:7: error: Revealed type is '__main__.User' +main:8: error: Revealed type is '__main__.User' [case testTypeUsingTypeCTypeVar] from typing import Type, TypeVar class User: pass -class ProUser: pass -class BasicUser: pass +class ProUser(User): pass +class BasicUser(user): pass U = TypeVar('U', bound=User) def new_user(user_class: Type[U]) -> U: return user_class() @@ -1447,3 +1448,18 @@ reveal_type(new_user(ProUser)) [out] main: note: At top level: main:8: error: Revealed type is '__main__.ProUser' + +[case testTypeUsingTypeCCovariance] +from typing import Type, TypeVar +class User: pass +class ProUser(User): pass +class BasicUser(user): pass +U = TypeVar('U', bound=User) +def new_user(user_class: Type[U]) -> U: + return user_class() +def new_pro_user(user_class: Type[ProUser]): + user = new_user(user_class) + reveal_type(user) +[out] +main: note: At top level: +main:10: error: Revealed type is '__main__.ProUser' diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 80af64aecde73..7ef88083fc9b5 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, @@ -118,8 +118,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if len(t.args) != 1: self.fail('Type[...] must have exactly one type argument', t) items = self.anal_array(t.args) - # Currently Type[t] is just an alias for t. - return items[0] + item = items[0] + return TypeType(item, t.line) elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. return sym.type_override @@ -213,6 +213,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.accept(self), t.line) + def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') if len(t.args) == 0: @@ -390,3 +393,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/types.py b/mypy/types.py index 1d2c231abc6e8..2e01f9d85bdd5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -410,6 +410,7 @@ class CallableType(FunctionLike): # Is this Callable[..., t] (with literal '...')? is_ellipsis_args = False # Is this callable constructed for the benefit of a classmethod's 'cls' argument? + # XXX Need to rename, it's also for Type[C]? is_classmethod_class = False # Was this type implicitly generated instead of explicitly specified by the user? implicit = False @@ -783,6 +784,30 @@ 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. + """ + + 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 +819,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 +830,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 +848,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 +867,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 +885,10 @@ 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') + + def visit_type_type(self, t: TypeType) -> T: + raise self._notimplemented_helper('type_type') class TypeTranslator(TypeVisitor[Type]): @@ -1060,6 +1088,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. From 1fd14f0949d800f255bf99043266f8e5a70a5d6b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 22 May 2016 17:45:20 -0700 Subject: [PATCH 04/23] WIP: Fix/new tests; implement more subtype checks. --- mypy/constraints.py | 5 ++++- mypy/expandtype.py | 5 ++++- mypy/messages.py | 5 ++++- mypy/subtypes.py | 15 +++++++++++++ mypy/test/data/check-classes.test | 37 ++++++++++++++++++++++++------- mypy/types.py | 3 +++ 6 files changed, 59 insertions(+), 11 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 4ea64f43bb656..f4c2348c2bba3 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 @@ -354,6 +354,9 @@ def visit_overloaded(self, type: Overloaded) -> List[Constraint]: res.extend(infer_constraints(t, self.actual, self.direction)) return res + def visit_type_type(self, type: TypeType) -> List[Constraint]: + return [] # XXX What else to do? + def neg_op(op: int) -> int: """Map SubtypeOf to SupertypeOf and vice versa.""" diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 60730b563bd65..6240e7105f57a 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,9 @@ 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: + return TypeType(t.item.accept(self)) + def expand_types(self, types: List[Type]) -> List[Type]: a = [] # type: List[Type] for t in types: diff --git a/mypy/messages.py b/mypy/messages.py index 3d7d559a8d5d8..f8afc7a877f5d 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: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 59a4ce0066587..4f2a95bf31afe 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -201,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. + # XXX Or left.is_type_obj()? + return left.is_concrete_type_obj() and is_subtype(left.items()[0].ret_type, right.item) else: return False @@ -212,6 +217,16 @@ 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): + # XXX Or is_type_obj()? + return right.is_concrete_type_obj() and is_subtype(left.item, right.ret_type) + # XXX Others? Union, Any, TypeVar + 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 739518d1472ec..3ffc85c801c7e 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1440,7 +1440,7 @@ main:8: error: Revealed type is '__main__.User' from typing import Type, TypeVar class User: pass class ProUser(User): pass -class BasicUser(user): pass +class BasicUser(User): pass U = TypeVar('U', bound=User) def new_user(user_class: Type[U]) -> U: return user_class() @@ -1453,13 +1453,34 @@ main:8: error: Revealed type is '__main__.ProUser' from typing import Type, TypeVar class User: pass class ProUser(User): pass -class BasicUser(user): pass -U = TypeVar('U', bound=User) -def new_user(user_class: Type[U]) -> U: +class BasicUser(User): pass +def new_user(user_class: Type[User]) -> User: return user_class() def new_pro_user(user_class: Type[ProUser]): - user = new_user(user_class) - reveal_type(user) + 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) +[out] +main: note: In function "foo": +main:6: error: Argument 1 to "new_user" has incompatible type Type[int]; expected Type[User] + +[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] -main: note: At top level: -main:10: error: Revealed type is '__main__.ProUser' diff --git a/mypy/types.py b/mypy/types.py index 2e01f9d85bdd5..2185566c381e6 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1180,6 +1180,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. From 371e42545fa188d4a94609f871623fb1e8c3391a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 23 May 2016 13:39:23 -0700 Subject: [PATCH 05/23] WIP: Towards supporting Type[T]. The TypeC tests no pass except for one ProUser* that should be ProUser; but something's seriously wrong because many other tests now fail. --- mypy/applytype.py | 4 ++++ mypy/checkexpr.py | 31 +++++++++++++++++++++++-------- mypy/constraints.py | 8 ++++---- mypy/expandtype.py | 3 ++- mypy/subtypes.py | 11 +++++------ mypy/test/data/check-classes.test | 13 +++++++++---- mypy/types.py | 3 +++ 7 files changed, 50 insertions(+), 23 deletions(-) diff --git a/mypy/applytype.py b/mypy/applytype.py index 29f2287ef39ca..431f1094aeb43 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -43,6 +43,10 @@ def apply_generic_arguments(callable: CallableType, types: List[Type], else: msg.incompatible_typevar_value(callable, i + 1, type, context) + # Translate constructor fn back to class. + if isinstance(type, CallableType) and type.is_type_obj(): + types[i] = type = type.ret_type + upper_bound = callable.variables[i].upper_bound if type and not mypy.subtypes.satisfies_upper_bound(type, upper_bound): msg.incompatible_typevar_value(callable, i + 1, type, context) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 59641d1ded4ba..2fecd3d20bd27 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -278,20 +278,35 @@ def check_call(self, callee: Type, args: List[Node], return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names, callable_node, arg_messages) elif isinstance(callee, TypeType): - item = self.analyze_type_type(callee.item, context) + item = self.analyze_type_type_callee(callee.item, context) 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(self, arg: Type, context: Context) -> Type: - """Given the arg from (x: Type[arg]), return the type for x.""" - if isinstance(arg, AnyType): + # What's a good name for this method? + 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(arg, Instance): - return type_object_type(arg.type, self.named_type) - if isinstance(arg, UnionType): - return UnionType([self.analyze_type_type(item, context) for item in arg.items], arg.line) + 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): + callee = callee.copy_modified(ret_type=item) + return callee + # XXX Do we need to handle other forms? # XXX Make a nicely formatted error message. self.msg.fail("XXX Bad arg to Type[]", context) diff --git a/mypy/constraints.py b/mypy/constraints.py index f4c2348c2bba3..59a16c88794cc 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -348,14 +348,14 @@ 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, type: TypeType) -> List[Constraint]: - return [] # XXX What else to do? + def visit_type_type(self, template: TypeType) -> List[Constraint]: + return infer_constraints(template.item, self.actual, self.direction) def neg_op(op: int) -> int: diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 6240e7105f57a..c0e52da101153 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -95,7 +95,8 @@ def visit_partial_type(self, t: PartialType) -> Type: return t def visit_type_type(self, t: TypeType) -> Type: - return TypeType(t.item.accept(self)) + item = t.item.accept(self) + return TypeType(item) def expand_types(self, types: List[Type]) -> List[Type]: a = [] # type: List[Type] diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 4f2a95bf31afe..639e6918f0ea1 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -144,10 +144,11 @@ def visit_callable_type(self, left: CallableType) -> bool: return all(is_subtype(left, item, self.check_type_parameter) for item in right.items()) elif isinstance(right, Instance): + if left.is_type_obj(): + return is_subtype(left.ret_type, right) return is_subtype(left.fallback, right) elif isinstance(right, TypeType): - # XXX Or left.is_type_obj()? - return left.is_concrete_type_obj() and is_subtype(left.ret_type, right.item) + return left.is_type_obj() and is_subtype(left.ret_type, right.item) else: return False @@ -204,8 +205,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: 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. - # XXX Or left.is_type_obj()? - return left.is_concrete_type_obj() and is_subtype(left.items()[0].ret_type, right.item) + return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item) else: return False @@ -222,8 +222,7 @@ def visit_type_type(self, left: TypeType) -> bool: if isinstance(right, TypeType): return is_subtype(left.item, right.item) if isinstance(right, CallableType): - # XXX Or is_type_obj()? - return right.is_concrete_type_obj() and is_subtype(left.item, right.ret_type) + return right.is_type_obj() and is_subtype(left.item, right.ret_type) # XXX Others? Union, Any, TypeVar return False diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index 3ffc85c801c7e..dc4969b2cd094 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1443,11 +1443,16 @@ class ProUser(User): pass class BasicUser(User): pass U = TypeVar('U', bound=User) def new_user(user_class: Type[U]) -> U: - return user_class() -reveal_type(new_user(ProUser)) -[out] + 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:8: error: Revealed type is 'U`-1' main: note: At top level: -main:8: error: Revealed type is '__main__.ProUser' +main:11: error: Revealed type is '__main__.ProUser' [case testTypeUsingTypeCCovariance] from typing import Type, TypeVar diff --git a/mypy/types.py b/mypy/types.py index 2185566c381e6..3a2323079bf5f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -967,6 +967,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 t + class TypeStrVisitor(TypeVisitor[str]): """Visitor for pretty-printing types into strings. From 96696ff4320e8cde99474471fbd0481acec3c895 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 24 May 2016 07:28:40 -0700 Subject: [PATCH 06/23] Fix things so all tests pass! --- mypy/applytype.py | 4 ---- mypy/constraints.py | 5 ++++- mypy/subtypes.py | 2 -- mypy/test/data/check-classes.test | 4 ++-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/mypy/applytype.py b/mypy/applytype.py index 431f1094aeb43..29f2287ef39ca 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -43,10 +43,6 @@ def apply_generic_arguments(callable: CallableType, types: List[Type], else: msg.incompatible_typevar_value(callable, i + 1, type, context) - # Translate constructor fn back to class. - if isinstance(type, CallableType) and type.is_type_obj(): - types[i] = type = type.ret_type - upper_bound = callable.variables[i].upper_bound if type and not mypy.subtypes.satisfies_upper_bound(type, upper_bound): msg.incompatible_typevar_value(callable, i + 1, type, context) diff --git a/mypy/constraints.py b/mypy/constraints.py index 59a16c88794cc..cde961e9705ac 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -355,7 +355,10 @@ def visit_overloaded(self, template: Overloaded) -> List[Constraint]: return res def visit_type_type(self, template: TypeType) -> List[Constraint]: - return infer_constraints(template.item, self.actual, self.direction) + if isinstance(self.actual, CallableType) and self.actual.is_type_obj(): + return infer_constraints(template.item, self.actual.ret_type, self.direction) + print("XXX Don't know what to do with type %s for template %s" % (self.actual, template)) + return [] # XXX ??? def neg_op(op: int) -> int: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 639e6918f0ea1..24232185c17ee 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -144,8 +144,6 @@ def visit_callable_type(self, left: CallableType) -> bool: return all(is_subtype(left, item, self.check_type_parameter) for item in right.items()) elif isinstance(right, Instance): - if left.is_type_obj(): - return is_subtype(left.ret_type, right) return is_subtype(left.fallback, right) elif isinstance(right, TypeType): return left.is_type_obj() and is_subtype(left.ret_type, right.item) diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index dc4969b2cd094..b93d71cdbca5b 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1447,12 +1447,12 @@ def new_user(user_class: Type[U]) -> U: reveal_type(user) return user pro_user = new_user(ProUser) -reveal_type(pro_user) +reveal_type(pro_user) # XXX Why is this ProUser*? [out] main: note: In function "new_user": main:8: error: Revealed type is 'U`-1' main: note: At top level: -main:11: error: Revealed type is '__main__.ProUser' +main:11: error: Revealed type is '__main__.ProUser*' [case testTypeUsingTypeCCovariance] from typing import Type, TypeVar From aa375958d973b891729c56b3f5dc913cb49c5257 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 24 May 2016 09:34:33 -0700 Subject: [PATCH 07/23] 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). --- mypy/checkexpr.py | 3 ++ mypy/constraints.py | 6 ++-- mypy/erasetype.py | 6 +++- mypy/expandtype.py | 3 ++ mypy/join.py | 10 +++++- mypy/meet.py | 10 +++++- mypy/sametypes.py | 9 +++++- mypy/subtypes.py | 3 ++ mypy/test/data/check-classes.test | 52 ++++++++++++++++++++++++------- mypy/typeanal.py | 6 ++-- mypy/types.py | 30 ++++++++++++++++-- 11 files changed, 115 insertions(+), 23 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2fecd3d20bd27..c2757aac4a6c8 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -303,11 +303,14 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: # but better than AnyType...), but replace the return type # with typevar. callee = self.analyze_type_type_callee(item.upper_bound, context) + # XXX What to do for a generic class? Maybe just reject. if isinstance(callee, CallableType): callee = callee.copy_modified(ret_type=item) + # XXX What to do for Overloaded? return callee # XXX Do we need to handle other forms? + # XXX Or maybe those should be rejected during semantic analysis. # XXX Make a nicely formatted error message. self.msg.fail("XXX Bad arg to Type[]", context) return AnyType() diff --git a/mypy/constraints.py b/mypy/constraints.py index cde961e9705ac..1db60e3520562 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -357,8 +357,10 @@ def visit_overloaded(self, template: Overloaded) -> List[Constraint]: 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) - print("XXX Don't know what to do with type %s for template %s" % (self.actual, template)) - return [] # XXX ??? + elif isinstance(self.actual, TypeType): + return infer_constraints(template.item, self.actual.item, self.direction) + else: + return [] def neg_op(op: int) -> int: diff --git a/mypy/erasetype.py b/mypy/erasetype.py index f76533e2bafd7..e222838641545 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 ) @@ -72,6 +72,10 @@ 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: + # XXX No idea if this makes much sense. + return TypeType(AnyType(), 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 c0e52da101153..e25190fe43297 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -95,6 +95,9 @@ 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) diff --git a/mypy/join.py b/mypy/join.py index 2ae03602120bd..ef8efeb03f209 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 @@ -204,6 +205,13 @@ 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) + else: + # XXX Should at least try to join Type[] with type + return AnyType() + def join(self, s: Type, t: Type) -> Type: return join_types(s, t) diff --git a/mypy/meet.py b/mypy/meet.py index cbffed5b24a94..2af2dd6d258e6 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 @@ -207,6 +208,13 @@ 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): + return TypeType(self.meet(t.item, self.s.item), line=t.line) + else: + # XXX Other cases, e.g. type <--> Type[]? + return NoneTyp() + def meet(self, s, t): return meet_types(s, t) diff --git a/mypy/sametypes.py b/mypy/sametypes.py index bfe37a6a1394b..319af509e70cf 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 24232185c17ee..1b3b1d1581cfa 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -146,6 +146,7 @@ def visit_callable_type(self, left: CallableType) -> bool: 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 @@ -203,6 +204,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: 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 @@ -220,6 +222,7 @@ def visit_type_type(self, left: TypeType) -> bool: 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) # XXX Others? Union, Any, TypeVar return False diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index b93d71cdbca5b..531605144d9d3 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1427,38 +1427,47 @@ C(arg=0) from typing import Type class User: pass class ProUser(User): pass -class BasicUser(User): pass def new_user(user_class: Type[User]) -> User: return user_class() -reveal_type(new_user(User)) -reveal_type(new_user(ProUser)) +reveal_type(new_user(User)) # E: Revealed type is '__main__.User' +reveal_type(new_user(ProUser)) # E: Revealed type is '__main__.User' [out] -main:7: error: Revealed type is '__main__.User' -main:8: error: Revealed type is '__main__.User' [case testTypeUsingTypeCTypeVar] from typing import Type, TypeVar class User: pass class ProUser(User): pass -class BasicUser(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) # XXX Why is this ProUser*? +reveal_type(pro_user) [out] main: note: In function "new_user": -main:8: error: Revealed type is 'U`-1' +main:7: error: Revealed type is 'U`-1' main: note: At top level: -main:11: error: Revealed type is '__main__.ProUser*' +main:10: error: Revealed type is '__main__.ProUser*' + +[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) # E: Revealed type is '__main__.WizUser*' +[out] [case testTypeUsingTypeCCovariance] from typing import Type, TypeVar class User: pass class ProUser(User): pass -class BasicUser(User): pass def new_user(user_class: Type[User]) -> User: return user_class() def new_pro_user(user_class: Type[ProUser]): @@ -1471,10 +1480,9 @@ class User: pass def new_user(user_class: Type[User]): return user_class() def foo(arg: Type[int]): - new_user(arg) + new_user(arg) # E: Argument 1 to "new_user" has incompatible type Type[int]; expected Type[User] [out] main: note: In function "foo": -main:6: error: Argument 1 to "new_user" has incompatible type Type[int]; expected Type[User] [case testTypeUsingTypeCUnionOverload] from typing import Type, Union, overload @@ -1489,3 +1497,23 @@ 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() + reveal_type(x) # E: Revealed type is 'Any' +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": diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7ef88083fc9b5..bc0b29b2c81d8 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -115,11 +115,13 @@ def visit_unbound_type(self, t: UnboundType) -> Type: 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, t.line) + return TypeType(item, line=t.line) elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. return sym.type_override @@ -214,7 +216,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return AnyType() def visit_type_type(self, t: TypeType) -> Type: - return TypeType(t.accept(self), t.line) + return TypeType(t.accept(self), line=t.line) def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') diff --git a/mypy/types.py b/mypy/types.py index 3a2323079bf5f..700c00373a4a4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -789,11 +789,34 @@ class TypeType(Type): 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: + def __init__(self, item: Type, *, line: int = -1) -> None: super().__init__(line) self.item = item @@ -887,8 +910,9 @@ def visit_partial_type(self, t: PartialType) -> T: def visit_ellipsis_type(self, t: EllipsisType) -> T: raise self._notimplemented_helper('ellipsis_type') + @abstractmethod def visit_type_type(self, t: TypeType) -> T: - raise self._notimplemented_helper('type_type') + pass class TypeTranslator(TypeVisitor[Type]): @@ -968,7 +992,7 @@ def visit_overloaded(self, t: Overloaded) -> Type: return Overloaded(items=items) def visit_type_type(self, t: TypeType) -> Type: - return t + return TypeType(t.item.accept(self), line=t.line) class TypeStrVisitor(TypeVisitor[str]): From d4745b07643b32502a9f847bd031c0a9b9cc2af1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 May 2016 10:57:21 -0700 Subject: [PATCH 08/23] Improve equivalence between type and Type. --- mypy/subtypes.py | 3 +++ mypy/test/data/check-classes.test | 10 ++++++++++ mypy/test/data/fixtures/tuple.py | 4 +++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 1b3b1d1581cfa..ec25083025e33 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -224,6 +224,9 @@ def visit_type_type(self, left: TypeType) -> bool: 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() == 'builtins.type': + # Treat builtins.type the same as Type[Any]. + return True # XXX Others? Union, Any, TypeVar return False diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index 531605144d9d3..0f6e09c0a6944 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1517,3 +1517,13 @@ 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] diff --git a/mypy/test/data/fixtures/tuple.py b/mypy/test/data/fixtures/tuple.py index 158a812cd7ea4..76c1091273644 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 From cf4a0b7b427d301b60f4351d66e8d1589465249d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 May 2016 13:43:10 -0700 Subject: [PATCH 09/23] Add support for method lookup from Type[]. We only support two special cases (hopefully the most common ones): - Type[] - Type[] --- mypy/checkmember.py | 20 ++++++- mypy/test/data/check-classes.test | 72 ++++++++++++++++++++++++++ mypy/test/data/fixtures/classmethod.py | 1 + 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f6019e3a67212..e3a692c4e97b9 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/test/data/check-classes.test b/mypy/test/data/check-classes.test index 0f6e09c0a6944..535a0b0ed7dce 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1527,3 +1527,75 @@ def bar(arg: Type[X]): 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": diff --git a/mypy/test/data/fixtures/classmethod.py b/mypy/test/data/fixtures/classmethod.py index 634e1e0c30e8f..98cff8a15e2a2 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 From 81ee23993bae7cd0b97dcf5db3294328caacbaa1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 May 2016 17:22:58 -0700 Subject: [PATCH 10/23] Print a proper error for unsupported Type[] args. (This happens when type checking a call, not during semantic analysis.) --- mypy/checkexpr.py | 8 +++----- mypy/messages.py | 3 +++ mypy/test/data/check-classes.test | 8 ++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c2757aac4a6c8..7dbbe62955189 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -278,7 +278,8 @@ def check_call(self, callee: Type, args: List[Node], return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names, callable_node, arg_messages) elif isinstance(callee, TypeType): - item = self.analyze_type_type_callee(callee.item, context) + # 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: @@ -309,10 +310,7 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: # XXX What to do for Overloaded? return callee - # XXX Do we need to handle other forms? - # XXX Or maybe those should be rejected during semantic analysis. - # XXX Make a nicely formatted error message. - self.msg.fail("XXX Bad arg to Type[]", context) + self.msg.unsupported_type_type(item, context) return AnyType() def infer_arg_types_in_context(self, callee: CallableType, diff --git a/mypy/messages.py b/mypy/messages.py index f8afc7a877f5d..2946910696f71 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -831,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/test/data/check-classes.test b/mypy/test/data/check-classes.test index 535a0b0ed7dce..9d3ac7021c9cd 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1599,3 +1599,11 @@ def process(cls: Type[U]): [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": From 3d92049782bbbf238fbacb070fab636295ad36cd Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 May 2016 17:44:04 -0700 Subject: [PATCH 11/23] Reject Type[U] where U's bound is a generic class. --- mypy/checkexpr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 7dbbe62955189..0be7173284304 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -304,11 +304,12 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: # but better than AnyType...), but replace the return type # with typevar. callee = self.analyze_type_type_callee(item.upper_bound, context) - # XXX What to do for a generic class? Maybe just reject. - if isinstance(callee, CallableType): - callee = callee.copy_modified(ret_type=item) - # XXX What to do for Overloaded? - return callee + if not isinstance(callee, CallableType): + # Might be a union. + # XXX What to do for Overloaded? + return callee + if not callee.is_generic(): + return callee.copy_modified(ret_type=item) self.msg.unsupported_type_type(item, context) return AnyType() From 5ab11d25b97afb6d3f72608aff147af41778a055 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 May 2016 19:03:18 -0700 Subject: [PATCH 12/23] Support classes with @overload-ed __init__. --- mypy/checkexpr.py | 17 ++++++++++++----- mypy/constraints.py | 3 +++ mypy/test/data/check-classes.test | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0be7173284304..1f8f376092aad 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -304,12 +304,19 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: # but better than AnyType...), but replace the return type # with typevar. callee = self.analyze_type_type_callee(item.upper_bound, context) - if not isinstance(callee, CallableType): - # Might be a union. - # XXX What to do for Overloaded? + 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 - if not callee.is_generic(): - return callee.copy_modified(ret_type=item) self.msg.unsupported_type_type(item, context) return AnyType() diff --git a/mypy/constraints.py b/mypy/constraints.py index 1db60e3520562..8e5fdee06b702 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -357,6 +357,9 @@ def visit_overloaded(self, template: Overloaded) -> List[Constraint]: 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: diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index 9d3ac7021c9cd..d83ee650b7757 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1607,3 +1607,20 @@ def foo(arg: Type[Tuple[int]]): # E: Unsupported type Type["Tuple[int]"] [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() + return uc() +u = new(User) +[builtins fixtures/classmethod.py] +[out] From afb048245fc2d78d38c7e029c90087b21769dee1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 26 May 2016 10:28:09 -0700 Subject: [PATCH 13/23] Update our copies of typing.py to the latest from the python/typing repo. --- lib-typing/2.7/test_typing.py | 31 ++++++++++++++++++++++++++++++ lib-typing/2.7/typing.py | 36 ++++++++++++++++++++++++++++++++++- lib-typing/3.2/test_typing.py | 28 +++++++++++++++++++++++++++ lib-typing/3.2/typing.py | 35 +++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/lib-typing/2.7/test_typing.py b/lib-typing/2.7/test_typing.py index c11ba2210485d..b801383cd6465 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 44eb9f2efc683..adbf0389b8557 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 f9e54b2725fe1..ade8a3587de4d 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 841e778648a66..b7f3ffa0aa264 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. From db30d59f39c251782753c01221ddc58c5c3893a7 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 6 Jun 2016 16:13:15 -0700 Subject: [PATCH 14/23] Make Type[A] erase to Type[A], but Type[T] erases to Type[Any]. --- mypy/erasetype.py | 4 ++-- mypy/test/testtypes.py | 6 +++++- mypy/typefixture.py | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index e222838641545..e805f8563b0fb 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -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()) @@ -73,8 +74,7 @@ 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: - # XXX No idea if this makes much sense. - return TypeType(AnyType(), line=t.line) + return TypeType(t.item.accept(self), line=t.line) def erase_generic_types(t: Type) -> Type: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 754ed7271a71a..06e895f5ec24b 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, TypeType(AnyType())) + def assert_erase(self, orig, result): assert_equal(str(erase_type(orig)), str(result)) diff --git a/mypy/typefixture.py b/mypy/typefixture.py index ffd1e84d90bd4..7d9a69e1bdab0 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,9 @@ 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_t = TypeType(self.t) + # Helper methods def callable(self, *a): From 6029c9d619aa4755b0601dc59346f5722850eb41 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 6 Jun 2016 17:30:53 -0700 Subject: [PATCH 15/23] Special-case joining Type[] with builtins.type. Also add tests. --- mypy/join.py | 5 ++++- mypy/test/testtypes.py | 11 ++++++++++- mypy/typefixture.py | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index ef8efeb03f209..82384ab5b1b32 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -131,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) @@ -208,8 +210,9 @@ def visit_partial_type(self, t: PartialType) -> Type: 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: - # XXX Should at least try to join Type[] with type return AnyType() def join(self, s: Type, t: Type) -> Type: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 06e895f5ec24b..16cb3a10bbfa8 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -179,7 +179,7 @@ def test_erase_with_type_object(self): 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, TypeType(AnyType())) + self.assert_erase(self.fx.type_t, TypeType(self.fx.anyt)) def assert_erase(self, orig, result): assert_equal(str(erase_type(orig)), str(result)) @@ -495,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, TypeType(self.fx.anyt), TypeType(self.fx.anyt)) + 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, TypeType(self.fx.anyt), 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. diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 7d9a69e1bdab0..4369e4e1bdf65 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -142,6 +142,9 @@ def __init__(self, variance: int=COVARIANT) -> None: 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) # Helper methods From 786ef968aaa7f48d7fa64dd6ea9ffe06cc6d1c3b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 6 Jun 2016 17:55:47 -0700 Subject: [PATCH 16/23] Special-case joining Type[] with builtins.type. Also add tests. --- mypy/meet.py | 10 ++++++++-- mypy/test/testtypes.py | 19 ++++++++++++++----- mypy/typefixture.py | 1 + 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 2af2dd6d258e6..8328827fa902c 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -185,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) @@ -210,9 +212,13 @@ def visit_partial_type(self, t: PartialType) -> Type: def visit_type_type(self, t: TypeType) -> Type: if isinstance(self.s, TypeType): - return TypeType(self.meet(t.item, self.s.item), line=t.line) + 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: - # XXX Other cases, e.g. type <--> Type[]? return NoneTyp() def meet(self, s, t): diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 16cb3a10bbfa8..e4b8c0294900f 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -179,7 +179,7 @@ def test_erase_with_type_object(self): 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, TypeType(self.fx.anyt)) + 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)) @@ -497,11 +497,11 @@ def test_simple_type_objects(self): 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, TypeType(self.fx.anyt), TypeType(self.fx.anyt)) + 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, TypeType(self.fx.anyt), self.fx.type_type) + 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. @@ -691,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) @@ -712,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/typefixture.py b/mypy/typefixture.py index 4369e4e1bdf65..ec76cf80d68b1 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -146,6 +146,7 @@ def __init__(self, variance: int=COVARIANT) -> None: 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 From ef247f283e72bafa4e86331eeeb969719205686d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 6 Jun 2016 20:58:40 -0700 Subject: [PATCH 17/23] Delete outdated XXX comment. --- mypy/types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index 700c00373a4a4..92b86593c992c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -410,7 +410,6 @@ class CallableType(FunctionLike): # Is this Callable[..., t] (with literal '...')? is_ellipsis_args = False # Is this callable constructed for the benefit of a classmethod's 'cls' argument? - # XXX Need to rename, it's also for Type[C]? is_classmethod_class = False # Was this type implicitly generated instead of explicitly specified by the user? implicit = False From 41621035cb4bc42abe92e47bfb4ffa3c347efaca Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 6 Jun 2016 21:30:09 -0700 Subject: [PATCH 18/23] Finishing touch -- fix and test TypeAnalyser.visit_type_type(). --- mypy/subtypes.py | 1 - mypy/test/data/check-classes.test | 6 ++++++ mypy/typeanal.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index ec25083025e33..82182271c99ba 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -227,7 +227,6 @@ def visit_type_type(self, left: TypeType) -> bool: if isinstance(right, Instance) and right.type.fullname() == 'builtins.type': # Treat builtins.type the same as Type[Any]. return True - # XXX Others? Union, Any, TypeVar return False diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index d83ee650b7757..cb192a3b92dc7 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1624,3 +1624,9 @@ def new(uc: Type[U]) -> U: u = new(User) [builtins fixtures/classmethod.py] [out] + +[case testTypeUsingTypeCInUpperBound] +from typing import TypeVar, Type +class B: pass +T = TypeVar('T', bound=Type[B]) +def f(a: T): pass diff --git a/mypy/typeanal.py b/mypy/typeanal.py index bc0b29b2c81d8..6a7ef29cf9b13 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -216,7 +216,7 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return AnyType() def visit_type_type(self, t: TypeType) -> Type: - return TypeType(t.accept(self), line=t.line) + return TypeType(t.item.accept(self), line=t.line) def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') From 26ff26707db317d979de539693b65df21e35639d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 7 Jun 2016 10:28:32 -0700 Subject: [PATCH 19/23] Added tests as requested. --- mypy/test/data/check-classes.test | 52 +++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index cb192a3b92dc7..e9149ad28cf0c 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1433,6 +1433,26 @@ 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 @@ -1450,6 +1470,28 @@ 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 @@ -1461,8 +1503,14 @@ 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) # E: Revealed type is '__main__.WizUser*' -[out] +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 From 256b31ade337b5eda57f0c9709c4b5308a54647b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 7 Jun 2016 10:33:15 -0700 Subject: [PATCH 20/23] Remove comment asking for name for analyze_type_type_callee(). --- mypy/checkexpr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1f8f376092aad..64263ce6cfb25 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -285,7 +285,6 @@ def check_call(self, callee: Type, args: List[Node], else: return self.msg.not_callable(callee, context), AnyType() - # What's a good name for this method? def analyze_type_type_callee(self, item: Type, context: Context) -> Type: """Analyze the callee X in X(...) where X is Type[item]. From d224aff1db6d5eae67d20f7b893b2dbf9b455990 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 7 Jun 2016 10:59:05 -0700 Subject: [PATCH 21/23] Use self.default(self.s) instead of AnyType()/NonType() in join/meet. --- mypy/join.py | 2 +- mypy/meet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 82384ab5b1b32..0c42b64d7d622 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -213,7 +213,7 @@ def visit_type_type(self, t: TypeType) -> Type: elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': return self.s else: - return AnyType() + 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 8328827fa902c..eff2eedf8fab1 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -219,7 +219,7 @@ def visit_type_type(self, t: TypeType) -> Type: elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': return t else: - return NoneTyp() + return self.default(self.s) def meet(self, s, t): return meet_types(s, t) From d9c74a0efe256e4e61fa6c32b17853cf416fc60f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 7 Jun 2016 11:04:19 -0700 Subject: [PATCH 22/23] Add tests for overloaded __init__. --- mypy/test/data/check-classes.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index e9149ad28cf0c..72c047d66244a 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1668,10 +1668,19 @@ class User: 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 From eaa9e0d74d4a12f36a62ff6ee9ffd05a7d7af492 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 7 Jun 2016 11:54:48 -0700 Subject: [PATCH 23/23] Add more tests. Fixed a bug in join() this found. --- mypy/subtypes.py | 6 ++++-- mypy/test/data/check-classes.test | 36 +++++++++++++++++++++++++++++++ mypy/test/data/fixtures/list.py | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 82182271c99ba..ea47c3f67b5de 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -224,8 +224,10 @@ def visit_type_type(self, left: TypeType) -> bool: 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() == 'builtins.type': - # Treat builtins.type the same as Type[Any]. + 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 diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index 72c047d66244a..bf501819a3c67 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1550,7 +1550,10 @@ bar(Y) 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] @@ -1687,3 +1690,36 @@ 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/list.py b/mypy/test/data/fixtures/list.py index a50fa7ce6dfbc..55c8999cb46a2 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