From 04934c3411c1771b3c0a3aa5a70b99a34bbcc92f Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 00:00:02 +0300 Subject: [PATCH 01/47] move things around --- mypy/checker.py | 9 +++--- mypy/checkexpr.py | 3 +- mypy/checkmember.py | 7 ++--- mypy/constraints.py | 2 +- mypy/nodes.py | 51 ------------------------------- mypy/semanal.py | 11 +++++-- mypy/solve.py | 9 +++--- mypy/typeanal.py | 2 ++ mypy/types.py | 51 +++++++++++++++++++++++++++++++ test-data/unit/check-classes.test | 18 +++++++++++ 10 files changed, 92 insertions(+), 71 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d9c9f5ddbecf..6df116adfabf 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -29,13 +29,12 @@ AwaitExpr, CONTRAVARIANT, COVARIANT ) -from mypy.nodes import function_type, method_type, method_type_with_fallback from mypy import nodes from mypy.types import ( Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, - true_only, false_only + true_only, false_only, function_type, method_type_with_fallback ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -840,7 +839,7 @@ def check_method_override_for_base_with_name( assert False, str(base_attr.node) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( - method_type(original_type), + original_type.method_type(), defn.info, base) # Check that the types are compatible. # TODO overloaded signatures @@ -979,8 +978,8 @@ def check_compatibility(self, name: str, base1: TypeInfo, if (isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike)): # Method override - first_sig = method_type(first_type) - second_sig = method_type(second_type) + first_sig = first_type.method_type() + second_sig = second_type.method_type() ok = is_subtype(first_sig, second_sig) elif first_type and second_type: ok = is_equivalent(first_type, second_type) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 21900ba7efd0..2d43fa120d18 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -6,7 +6,7 @@ Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef, TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, - true_only, false_only, is_named_instance + true_only, false_only, is_named_instance, function_type ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -18,7 +18,6 @@ DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF, ) -from mypy.nodes import function_type from mypy import nodes import mypy.checker from mypy import types diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 7241b725b254..7a62370aac70 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -5,15 +5,14 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarId, TypeVarDef, Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType, - DeletedType, NoneTyp, TypeType + DeletedType, NoneTyp, TypeType, function_type, method_type_with_fallback ) from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, OpExpr, ComparisonExpr -from mypy.nodes import function_type, Decorator, OverloadedFuncDef +from mypy.nodes import Decorator, OverloadedFuncDef from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance -from mypy.nodes import method_type, method_type_with_fallback from mypy.semanal import self_type from mypy import messages from mypy import subtypes @@ -245,7 +244,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = method_type(functype) + signature = functype.method_type() if var.is_property: # A property cannot have an overloaded type => the cast # is fine. diff --git a/mypy/constraints.py b/mypy/constraints.py index f204c026a30a..e26e583522ab 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -1,6 +1,6 @@ """Type inference constraints.""" -from typing import List, Optional, cast +from typing import List, Optional from mypy.types import ( CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType, diff --git a/mypy/nodes.py b/mypy/nodes.py index dff502e47199..53cdd934bdf4 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2209,57 +2209,6 @@ def deserialize(cls, data: JsonDict) -> 'SymbolTable': return st -def function_type(func: FuncBase, fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': - if func.type: - assert isinstance(func.type, mypy.types.FunctionLike) - return func.type - else: - # Implicit type signature with dynamic types. - # Overloaded functions always have a signature, so func must be an ordinary function. - fdef = cast(FuncDef, func) - name = func.name() - if name: - name = '"{}"'.format(name) - - return mypy.types.CallableType( - [mypy.types.AnyType()] * len(fdef.arg_names), - fdef.arg_kinds, - fdef.arg_names, - mypy.types.AnyType(), - fallback, - name, - implicit=True, - ) - - -def method_type_with_fallback(func: FuncBase, - fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': - """Return the signature of a method (omit self).""" - return method_type(function_type(func, fallback)) - - -def method_type(sig: 'mypy.types.FunctionLike') -> 'mypy.types.FunctionLike': - if isinstance(sig, mypy.types.CallableType): - return method_callable(sig) - else: - sig = cast(mypy.types.Overloaded, sig) - items = [] # type: List[mypy.types.CallableType] - for c in sig.items(): - items.append(method_callable(c)) - return mypy.types.Overloaded(items) - - -def method_callable(c: 'mypy.types.CallableType') -> 'mypy.types.CallableType': - if c.arg_kinds and c.arg_kinds[0] == ARG_STAR: - # The signature is of the form 'def foo(*args, ...)'. - # In this case we shouldn't drop the first arg, - # since self will be absorbed by the *args. - return c - return c.copy_modified(arg_types=c.arg_types[1:], - arg_kinds=c.arg_kinds[1:], - arg_names=c.arg_names[1:]) - - class MroError(Exception): """Raised if a consistent mro cannot be determined for a class.""" diff --git a/mypy/semanal.py b/mypy/semanal.py index 511e9adbdbf1..6f3d84f9b9b3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -72,8 +72,9 @@ from mypy.types import ( NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, FunctionLike, UnboundType, TypeList, TypeVarDef, - replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType) -from mypy.nodes import function_type, implicit_module_attrs + replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType, + function_type) +from mypy.nodes import implicit_module_attrs from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.sametypes import is_same_type @@ -331,7 +332,11 @@ def prepare_method_signature(self, func: FuncDef) -> None: if func.is_class: leading_type = self.class_type(self.type) else: - leading_type = self_type(self.type) + leading_type = func.arguments[0].type_annotation + if leading_type is not None: + print(leading_type) + else: + leading_type = self_type(self.type) func.type = replace_implicit_first_type(sig, leading_type) def is_conditional_func(self, previous: Node, new: FuncDef) -> bool: diff --git a/mypy/solve.py b/mypy/solve.py index 1ebeb923f5a3..2d24acf00525 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -1,6 +1,7 @@ """Type inference constraint solving""" -from typing import List, Dict +from typing import List, Dict, DefaultDict +from collections import defaultdict from mypy.types import Type, Void, NoneTyp, AnyType, ErrorType, UninhabitedType, TypeVarId from mypy.constraints import Constraint, SUPERTYPE_OF @@ -23,11 +24,9 @@ def solve_constraints(vars: List[TypeVarId], constraints: List[Constraint], pick AnyType. """ # Collect a list of constraints for each type variable. - cmap = {} # type: Dict[TypeVarId, List[Constraint]] + cmap = defaultdict(list) # type: DefaultDict[TypeVarId, List[Constraint]] for con in constraints: - a = cmap.get(con.type_var, []) # type: List[Constraint] - a.append(con) - cmap[con.type_var] = a + cmap[con.type_var].append(con) res = [] # type: List[Type] diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0fe5c451f2b2..557691b7f014 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -107,6 +107,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: elif fullname == 'typing.Any': return AnyType() elif fullname == 'typing.Tuple': + if len(t.args) == 0: + t.args = [AnyType(), EllipsisType()] if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) node = self.lookup_fqn_func('builtins.tuple') diff --git a/mypy/types.py b/mypy/types.py index 0b6c46970e7f..e2cb64b0e5de 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -513,6 +513,9 @@ def with_name(self, name: str) -> 'FunctionLike': pass def deserialize(cls, data: JsonDict) -> 'FunctionLike': return cast(FunctionLike, super().deserialize(data)) + @abstractmethod + def method_type(self) -> 'FunctionLike': pass + _dummy = object() # type: Any @@ -678,6 +681,19 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': implicit=data['implicit'], is_classmethod_class=data['is_classmethod_class'], ) + + def method_callable(self) -> 'CallableType': + if self.arg_kinds and self.arg_kinds[0] == mypy.nodes.ARG_STAR: + # The signature is of the form 'def foo(*args, ...)'. + # In this case we shouldn't drop the first arg, + # since self will be absorbed by the *args. + return self + return self.copy_modified(arg_types=self.arg_types[1:], + arg_kinds=self.arg_kinds[1:], + arg_names=self.arg_names[1:]) + + def method_type(self) -> 'CallableType': + return self.method_callable() class Overloaded(FunctionLike): @@ -731,6 +747,12 @@ def deserialize(self, data: JsonDict) -> 'Overloaded': assert data['.class'] == 'Overloaded' return Overloaded([CallableType.deserialize(t) for t in data['items']]) + def method_type(self) -> 'FunctionLike': + items = [] # type: List[mypy.types.CallableType] + for c in self.items(): + items.append(c.method_callable()) + return Overloaded(items) + class TupleType(Type): """The tuple type Tuple[T1, ..., Tn] (at least one type argument). @@ -1488,3 +1510,32 @@ def true_or_false(t: Type) -> Type: new_t.can_be_true = type(new_t).can_be_true new_t.can_be_false = type(new_t).can_be_false return new_t + + +def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike: + if func.type: + assert isinstance(func.type, mypy.types.FunctionLike) + return func.type + else: + # Implicit type signature with dynamic types. + # Overloaded functions always have a signature, so func must be an ordinary function. + fdef = cast(mypy.nodes.FuncDef, func) + name = func.name() + if name: + name = '"{}"'.format(name) + + return mypy.types.CallableType( + [mypy.types.AnyType()] * len(fdef.arg_names), + fdef.arg_kinds, + fdef.arg_names, + mypy.types.AnyType(), + fallback, + name, + implicit=True, + ) + + +def method_type_with_fallback(func: mypy.nodes.FuncBase, + fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': + """Return the signature of a method (omit self).""" + return function_type(func, fallback).method_type() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7533c67ffaa9..1909ba75cacb 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2065,3 +2065,21 @@ class A: pass class B(object, A): # E: Cannot determine consistent method resolution order (MRO) for "B" def readlines(self): pass __iter__ = readlines + +[case testSelfType] +from typing import TypeVar + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + pass + +reveal_type(A.copy) +reveal_type(B.copy) +reveal_type(A().copy) +reveal_type(B().copy) +reveal_type(A().copy()) +reveal_type(B().copy()) From 4eb5f75a2cf436ce2d45def72956dc2bd30377cf Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 04:47:47 +0300 Subject: [PATCH 02/47] first test pass. no override yet --- mypy/checker.py | 15 ++++----- mypy/checkmember.py | 30 ++++++++--------- mypy/nodes.py | 5 ++- mypy/semanal.py | 15 +++------ mypy/types.py | 55 +++++++++++++------------------ test-data/unit/check-classes.test | 27 +++++++++++---- 6 files changed, 72 insertions(+), 75 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6df116adfabf..f282dfbb0859 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -34,7 +34,7 @@ Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, - true_only, false_only, function_type, method_type_with_fallback + true_only, false_only, function_type ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -746,7 +746,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: method = defn.name() if method not in nodes.inplace_operator_methods: return - typ = self.method_type(defn) + typ = self.function_type(defn).bind_self(defn.info) cls = defn.info other_method = '__' + method[3:] if cls.has_readable_member(other_method): @@ -826,7 +826,7 @@ def check_method_override_for_base_with_name( # The name of the method is defined in the base class. # Construct the type of the overriding method. - typ = self.method_type(defn) + typ = self.function_type(defn).bind_self(defn.info) # Map the overridden method type to subtype context so that # it can be checked for compatibility. original_type = base_attr.type @@ -839,7 +839,7 @@ def check_method_override_for_base_with_name( assert False, str(base_attr.node) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( - original_type.method_type(), + original_type.bind_self(defn.info), defn.info, base) # Check that the types are compatible. # TODO overloaded signatures @@ -978,8 +978,8 @@ def check_compatibility(self, name: str, base1: TypeInfo, if (isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike)): # Method override - first_sig = first_type.method_type() - second_sig = second_type.method_type() + first_sig = first_type.bind_self(base1) + second_sig = second_type.bind_self(base1) ok = is_subtype(first_sig, second_sig) elif first_type and second_type: ok = is_equivalent(first_type, second_type) @@ -2351,9 +2351,6 @@ def iterable_item_type(self, instance: Instance) -> Type: def function_type(self, func: FuncBase) -> FunctionLike: return function_type(func, self.named_type('builtins.function')) - def method_type(self, func: FuncBase) -> FunctionLike: - return method_type_with_fallback(func, self.named_type('builtins.function')) - # Data structure returned by find_isinstance_check representing # information learned from the truth or falsehood of a condition. The diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 7a62370aac70..8507f406e2fd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1,14 +1,14 @@ """Type checking of attribute access""" -from typing import cast, Callable, List, Dict, Optional +from typing import cast, Callable, List, Optional, TYPE_CHECKING from mypy.types import ( - Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarId, TypeVarDef, - Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType, - DeletedType, NoneTyp, TypeType, function_type, method_type_with_fallback + Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarDef, + Overloaded, TypeVarType, UnionType, PartialType, + DeletedType, NoneTyp, TypeType, function_type ) from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile -from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, OpExpr, ComparisonExpr +from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2 from mypy.nodes import Decorator, OverloadedFuncDef from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype @@ -16,7 +16,7 @@ from mypy.semanal import self_type from mypy import messages from mypy import subtypes -if False: # import for forward declaration only +if TYPE_CHECKING: # import for forward declaration only import mypy.checker @@ -63,13 +63,13 @@ def analyze_member_access(name: str, not_ready_callback) if is_lvalue: msg.cant_assign_to_method(node) - typ = map_instance_to_supertype(typ, method.info) + signature = function_type(method, builtin_type('builtins.function')) if name == '__new__': - # __new__ is special and behaves like a static method -- don't strip - # the first argument. - signature = function_type(method, builtin_type('builtins.function')) + # __new__ is special and behaves like a static method -- don't strip the first argument. + pass else: - signature = method_type_with_fallback(method, builtin_type('builtins.function')) + signature = signature.bind_self(typ) + typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: # Not a method. @@ -195,8 +195,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: typ = map_instance_to_supertype(itype, method.info) - getattr_type = expand_type_by_instance( - method_type_with_fallback(method, builtin_type('builtins.function')), typ) + bound_method = function_type(method, builtin_type('builtins.function')).bind_self(info) + getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): return getattr_type.ret_type @@ -244,7 +244,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = functype.method_type() + signature = functype.bind_self(info) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -422,7 +422,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo, fallback: Instance) -> FunctionLike: - signature = method_type_with_fallback(init_or_new, fallback) + signature = function_type(init_or_new, fallback).bind_self(info) # The __init__ method might come from a generic superclass # (init_or_new.info) with type variables that do not map diff --git a/mypy/nodes.py b/mypy/nodes.py index 53cdd934bdf4..8265d78dace0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1,8 +1,7 @@ """Abstract syntax tree node classes (i.e. parse tree).""" import os -import re -from abc import abstractmethod, ABCMeta +from abc import abstractmethod from typing import ( Any, TypeVar, List, Tuple, cast, Set, Dict, Union, Optional @@ -1631,9 +1630,9 @@ def accept(self, visitor: NodeVisitor[T]) -> T: # # If T is contravariant in Foo[T], Foo[object] is a subtype of # Foo[int], but not vice versa. +CONTRAVARIANT = -1 # type: int INVARIANT = 0 # type: int COVARIANT = 1 # type: int -CONTRAVARIANT = 2 # type: int class TypeVarExpr(SymbolNode, Expression): diff --git a/mypy/semanal.py b/mypy/semanal.py index 6f3d84f9b9b3..b0cdd17478c7 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -72,8 +72,7 @@ from mypy.types import ( NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, FunctionLike, UnboundType, TypeList, TypeVarDef, - replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType, - function_type) + TupleType, UnionType, StarType, EllipsisType, function_type) from mypy.nodes import implicit_module_attrs from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -327,16 +326,12 @@ def prepare_method_signature(self, func: FuncDef) -> None: if not func.is_static: if not func.arguments: self.fail('Method must have at least one argument', func) - elif func.type: - sig = cast(FunctionLike, func.type) + elif func.type and func.arguments[0].type_annotation is None: if func.is_class: leading_type = self.class_type(self.type) else: - leading_type = func.arguments[0].type_annotation - if leading_type is not None: - print(leading_type) - else: - leading_type = self_type(self.type) + leading_type = self_type(self.type) + sig = cast(FunctionLike, func.type) func.type = replace_implicit_first_type(sig, leading_type) def is_conditional_func(self, previous: Node, new: FuncDef) -> bool: @@ -2905,7 +2900,7 @@ def self_type(typ: TypeInfo) -> Union[Instance, TupleType]: def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: if isinstance(sig, CallableType): - return replace_leading_arg_type(sig, new) + return sig.copy_modified(arg_types=[new] + sig.arg_types[1:]) else: sig = cast(Overloaded, sig) return Overloaded([cast(CallableType, replace_implicit_first_type(i, new)) diff --git a/mypy/types.py b/mypy/types.py index e2cb64b0e5de..81576e814df0 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -7,7 +7,7 @@ ) import mypy.nodes -from mypy.nodes import INVARIANT, SymbolNode +from mypy.nodes import INVARIANT, COVARIANT, SymbolNode from mypy import experiments @@ -514,7 +514,7 @@ def deserialize(cls, data: JsonDict) -> 'FunctionLike': return cast(FunctionLike, super().deserialize(data)) @abstractmethod - def method_type(self) -> 'FunctionLike': pass + def bind_self(self, self_type=None) -> 'FunctionLike': pass _dummy = object() # type: Any @@ -681,19 +681,26 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': implicit=data['implicit'], is_classmethod_class=data['is_classmethod_class'], ) - - def method_callable(self) -> 'CallableType': + + def bind_self(self, self_type: Type = None) -> 'CallableType': if self.arg_kinds and self.arg_kinds[0] == mypy.nodes.ARG_STAR: # The signature is of the form 'def foo(*args, ...)'. # In this case we shouldn't drop the first arg, # since self will be absorbed by the *args. return self + ret_type = self.ret_type + variables = self.variables + if self.arg_types and self_type is not None: + self_arg = self.arg_types[0] + if isinstance(self_arg, TypeVarType) and isinstance(ret_type, TypeVarType): + if self_arg.name == ret_type.name: + ret_type = self_type + variables = variables[1:] return self.copy_modified(arg_types=self.arg_types[1:], arg_kinds=self.arg_kinds[1:], - arg_names=self.arg_names[1:]) - - def method_type(self) -> 'CallableType': - return self.method_callable() + arg_names=self.arg_names[1:], + variables=variables, + ret_type=ret_type) class Overloaded(FunctionLike): @@ -747,11 +754,8 @@ def deserialize(self, data: JsonDict) -> 'Overloaded': assert data['.class'] == 'Overloaded' return Overloaded([CallableType.deserialize(t) for t in data['items']]) - def method_type(self) -> 'FunctionLike': - items = [] # type: List[mypy.types.CallableType] - for c in self.items(): - items.append(c.method_callable()) - return Overloaded(items) + def bind_self(self, self_type: Type = None) -> 'FunctionLike': + return Overloaded([c.bind_self(self_type) for c in self.items()]) class TupleType(Type): @@ -1441,14 +1445,6 @@ def strip_type(typ: Type) -> Type: return typ -def replace_leading_arg_type(t: CallableType, self_type: Type) -> CallableType: - """Return a copy of a callable type with a different self argument type. - - Assume that the callable is the signature of a method. - """ - return t.copy_modified(arg_types=[self_type] + t.arg_types[1:]) - - def is_named_instance(t: Type, fullname: str) -> bool: return (isinstance(t, Instance) and t.type is not None and @@ -1514,28 +1510,23 @@ def true_or_false(t: Type) -> Type: def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike: if func.type: - assert isinstance(func.type, mypy.types.FunctionLike) + assert isinstance(func.type, FunctionLike) return func.type else: # Implicit type signature with dynamic types. # Overloaded functions always have a signature, so func must be an ordinary function. - fdef = cast(mypy.nodes.FuncDef, func) + assert isinstance(func, mypy.nodes.FuncItem), str(func) + fdef = cast(mypy.nodes.FuncItem, func) name = func.name() if name: name = '"{}"'.format(name) - return mypy.types.CallableType( - [mypy.types.AnyType()] * len(fdef.arg_names), + return CallableType( + [AnyType()] * len(fdef.arg_names), fdef.arg_kinds, fdef.arg_names, - mypy.types.AnyType(), + AnyType(), fallback, name, implicit=True, ) - - -def method_type_with_fallback(func: mypy.nodes.FuncBase, - fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': - """Return the signature of a method (omit self).""" - return function_type(func, fallback).method_type() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1909ba75cacb..f23e850024fa 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2077,9 +2077,24 @@ class A: class B(A): pass -reveal_type(A.copy) -reveal_type(B.copy) -reveal_type(A().copy) -reveal_type(B().copy) -reveal_type(A().copy()) -reveal_type(B().copy()) +reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' +reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' +reveal_type(A().copy()) # E: Revealed type is '__main__.A' +reveal_type(B().copy()) # E: Revealed type is '__main__.B' + +# [case testSelfTypeOverride] +# from typing import TypeVar +# +# T = TypeVar('T') +# +# class A: +# def copy(self: T) -> T: ... +# +# class B(A): +# pass +# +# class C(B): +# def copy(self: T) -> T: ... +# +-- # reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.B' +-- # reveal_type(C().copy()) # E: Revealed type is '__main__.C' \ No newline at end of file From 181ff0963578dcff858c30bd1acfc433be4fd9f4 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 05:22:04 +0300 Subject: [PATCH 03/47] selftypeClass test pass --- mypy/checkmember.py | 23 +++++++++-------------- mypy/types.py | 12 ++++++++---- test-data/unit/check-classes.test | 22 +++++++++++++++++++++- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 8507f406e2fd..817f90ced055 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -195,7 +195,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: typ = map_instance_to_supertype(itype, method.info) - bound_method = function_type(method, builtin_type('builtins.function')).bind_self(info) + bound_method = function_type(method, builtin_type('builtins.function')).bind_self(itype) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): return getattr_type.ret_type @@ -244,7 +244,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = functype.bind_self(info) + signature = functype.bind_self() if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -342,7 +342,7 @@ def analyze_class_attribute_access(itype: Instance, if isinstance(t, PartialType): return handle_partial_attribute_type(t, is_lvalue, msg, node.node) is_classmethod = is_decorated and cast(Decorator, node.node).func.is_class - return add_class_tvars(t, itype.type, is_classmethod, builtin_type) + return add_class_tvars(t, itype, is_classmethod, builtin_type) elif isinstance(node.node, Var): not_ready_callback(name, context) return AnyType() @@ -361,23 +361,18 @@ def analyze_class_attribute_access(itype: Instance, return function_type(cast(FuncBase, node.node), builtin_type('builtins.function')) -def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool, +def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, builtin_type: Callable[[str], Instance]) -> Type: + info = itype.type # type: TypeInfo if isinstance(t, CallableType): # TODO: Should we propagate type variable values? vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] - arg_types = t.arg_types - arg_kinds = t.arg_kinds - arg_names = t.arg_names if is_classmethod: - arg_types = arg_types[1:] - arg_kinds = arg_kinds[1:] - arg_names = arg_names[1:] - return t.copy_modified(arg_types=arg_types, arg_kinds=arg_kinds, arg_names=arg_names, - variables=vars + t.variables) + t = t.bind_self(itype) + return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): - return Overloaded([cast(CallableType, add_class_tvars(i, info, is_classmethod, + return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, builtin_type)) for i in t.items()]) return t @@ -422,7 +417,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo, fallback: Instance) -> FunctionLike: - signature = function_type(init_or_new, fallback).bind_self(info) + signature = function_type(init_or_new, fallback).bind_self() # The __init__ method might come from a generic superclass # (init_or_new.info) with type variables that do not map diff --git a/mypy/types.py b/mypy/types.py index 81576e814df0..a6ba0baae80f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -689,13 +689,17 @@ def bind_self(self, self_type: Type = None) -> 'CallableType': # since self will be absorbed by the *args. return self ret_type = self.ret_type - variables = self.variables + variables = list(self.variables) if self.arg_types and self_type is not None: self_arg = self.arg_types[0] - if isinstance(self_arg, TypeVarType) and isinstance(ret_type, TypeVarType): - if self_arg.name == ret_type.name: + if isinstance(self_arg, TypeType): + print(self_arg) + self_arg = self_arg.item + print(self_arg) + if isinstance(self_arg, TypeVarType): + variables = variables[1:] + if isinstance(ret_type, TypeVarType) and self_arg.name == ret_type.name: ret_type = self_type - variables = variables[1:] return self.copy_modified(arg_types=self.arg_types[1:], arg_kinds=self.arg_kinds[1:], arg_names=self.arg_names[1:], diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f23e850024fa..cb5a14c04aa0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2066,7 +2066,7 @@ class B(object, A): # E: Cannot determine consistent method resolution order (MR def readlines(self): pass __iter__ = readlines -[case testSelfType] +[case testSelfTypeInstance] from typing import TypeVar T = TypeVar('T') @@ -2082,6 +2082,26 @@ reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' reveal_type(A().copy()) # E: Revealed type is '__main__.A' reveal_type(B().copy()) # E: Revealed type is '__main__.B' +[case testSelfTypeClass] +from typing import TypeVar, Type + +T = TypeVar('T') + +class A: + @classmethod + def new(cls: Type[T]) -> T: + return cls() + +class B(A): + pass + +reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A' +reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B' +reveal_type(A.new()) # E: Revealed type is '__main__.A' +reveal_type(B.new()) # E: Revealed type is '__main__.B' + +[builtins fixtures/classmethod.pyi] + # [case testSelfTypeOverride] # from typing import TypeVar # From 25e104a72b47d55e278626835ea7a97600c9758d Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 05:42:54 +0300 Subject: [PATCH 04/47] Simple override --- mypy/checker.py | 10 +++++----- mypy/nodes.py | 2 +- test-data/unit/check-classes.test | 32 +++++++++++++++---------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f282dfbb0859..cb14e8b8451a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -746,7 +746,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: method = defn.name() if method not in nodes.inplace_operator_methods: return - typ = self.function_type(defn).bind_self(defn.info) + typ = self.function_type(defn).bind_self() cls = defn.info other_method = '__' + method[3:] if cls.has_readable_member(other_method): @@ -826,7 +826,7 @@ def check_method_override_for_base_with_name( # The name of the method is defined in the base class. # Construct the type of the overriding method. - typ = self.function_type(defn).bind_self(defn.info) + typ = self.function_type(defn).bind_self() # Map the overridden method type to subtype context so that # it can be checked for compatibility. original_type = base_attr.type @@ -839,7 +839,7 @@ def check_method_override_for_base_with_name( assert False, str(base_attr.node) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( - original_type.bind_self(defn.info), + original_type.bind_self(), defn.info, base) # Check that the types are compatible. # TODO overloaded signatures @@ -978,8 +978,8 @@ def check_compatibility(self, name: str, base1: TypeInfo, if (isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike)): # Method override - first_sig = first_type.bind_self(base1) - second_sig = second_type.bind_self(base1) + first_sig = first_type.bind_self() + second_sig = second_type.bind_self() ok = is_subtype(first_sig, second_sig) elif first_type and second_type: ok = is_equivalent(first_type, second_type) diff --git a/mypy/nodes.py b/mypy/nodes.py index 8265d78dace0..dcb49af1e63b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -113,7 +113,7 @@ def get_line(self) -> int: return self.line def accept(self, visitor: NodeVisitor[T]) -> T: - raise RuntimeError('Not implemented') + raise RuntimeError('Not implemented: {}.accept({})'.format(type(self), type(visitor))) # These are placeholders for a future refactoring; see #1783. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index cb5a14c04aa0..ab632d78eaaa 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2102,19 +2102,19 @@ reveal_type(B.new()) # E: Revealed type is '__main__.B' [builtins fixtures/classmethod.pyi] -# [case testSelfTypeOverride] -# from typing import TypeVar -# -# T = TypeVar('T') -# -# class A: -# def copy(self: T) -> T: ... -# -# class B(A): -# pass -# -# class C(B): -# def copy(self: T) -> T: ... -# --- # reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.B' --- # reveal_type(C().copy()) # E: Revealed type is '__main__.C' \ No newline at end of file +[case testSelfTypeOverride] +from typing import TypeVar + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + def copy(self: T) -> T: ... + +class C(A): + def copy(self: T) -> T: ... + +reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' +reveal_type(C().copy()) # E: Revealed type is '__main__.C' From 5e726236d23fc280ee63c9540b21230d38aeea60 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 05:44:14 +0300 Subject: [PATCH 05/47] Simple override --- test-data/unit/check-classes.test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ab632d78eaaa..c70f3d5417b6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2111,10 +2111,12 @@ class A: def copy(self: T) -> T: ... class B(A): - def copy(self: T) -> T: ... + pass class C(A): def copy(self: T) -> T: ... - + reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' reveal_type(C().copy()) # E: Revealed type is '__main__.C' +reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' +reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' From 29d2e0ff65fa4bfe8e9c1f2239c47ef791b56564 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 06:06:46 +0300 Subject: [PATCH 06/47] test super() and rename self_type() --- mypy/checker.py | 4 ++-- mypy/checkexpr.py | 4 ++-- mypy/checkmember.py | 12 +++++++----- mypy/semanal.py | 6 +++--- test-data/unit/check-classes.test | 17 ++++++++++++++++- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cb14e8b8451a..0bdf183fe747 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -47,7 +47,7 @@ is_more_precise, restrict_subtype_away ) from mypy.maptype import map_instance_to_supertype -from mypy.semanal import self_type, set_callable_name, refers_to_fullname +from mypy.semanal import fill_typevars, set_callable_name, refers_to_fullname from mypy.erasetype import erase_typevars from mypy.expandtype import expand_type_by_instance, expand_type from mypy.visitor import NodeVisitor @@ -750,7 +750,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: cls = defn.info other_method = '__' + method[3:] if cls.has_readable_member(other_method): - instance = self_type(cls) + instance = fill_typevars(cls) typ2 = self.expr_checker.analyze_external_member_access( other_method, instance, defn) fail = False diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2d43fa120d18..8c69e791a09b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -31,7 +31,7 @@ from mypy import applytype from mypy import erasetype from mypy.checkmember import analyze_member_access, type_object_type -from mypy.semanal import self_type +from mypy.semanal import fill_typevars from mypy.constraints import get_actual_type from mypy.checkstrformat import StringFormatterChecker from mypy.expandtype import expand_type @@ -1606,7 +1606,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.typing_mode_full(): return AnyType() - return analyze_member_access(e.name, self_type(e.info), e, + return analyze_member_access(e.name, fill_typevars(e.info), e, is_lvalue, True, False, self.named_type, self.not_ready_callback, self.msg, base, chk=self.chk) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 817f90ced055..4fc8f88da6c4 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -13,7 +13,7 @@ from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance -from mypy.semanal import self_type +from mypy.semanal import fill_typevars from mypy import messages from mypy import subtypes if TYPE_CHECKING: # import for forward declaration only @@ -65,7 +65,8 @@ def analyze_member_access(name: str, msg.cant_assign_to_method(node) signature = function_type(method, builtin_type('builtins.function')) if name == '__new__': - # __new__ is special and behaves like a static method -- don't strip the first argument. + # __new__ is special and behaves like a static method + # -- don't strip the first argument. pass else: signature = signature.bind_self(typ) @@ -194,8 +195,9 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, if not is_lvalue: method = info.get_method('__getattr__') if method: + function = function_type(method, builtin_type('builtins.function')) + bound_method = function.bind_self(itype) typ = map_instance_to_supertype(itype, method.info) - bound_method = function_type(method, builtin_type('builtins.function')).bind_self(itype) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): return getattr_type.ret_type @@ -455,7 +457,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, variables.extend(init_type.variables) callable_type = init_type.copy_modified( - ret_type=self_type(info), fallback=type_type, name=None, variables=variables, + ret_type=fill_typevars(info), fallback=type_type, name=None, variables=variables, special_sig=special_sig) c = callable_type.with_name('"{}"'.format(info.name())) c.is_classmethod_class = True @@ -476,7 +478,7 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo, Now S in the context of D would be mapped to E[T] in the context of C. """ # Create the type of self in subtype, of form t[a1, ...]. - inst_type = self_type(sub_info) + inst_type = fill_typevars(sub_info) if isinstance(inst_type, TupleType): inst_type = inst_type.fallback # Map the type of self to supertype. This gets us a description of the diff --git a/mypy/semanal.py b/mypy/semanal.py index b0cdd17478c7..b385b1b07dd3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -330,7 +330,7 @@ def prepare_method_signature(self, func: FuncDef) -> None: if func.is_class: leading_type = self.class_type(self.type) else: - leading_type = self_type(self.type) + leading_type = fill_typevars(self.type) sig = cast(FunctionLike, func.type) func.type = replace_implicit_first_type(sig, leading_type) @@ -1737,7 +1737,7 @@ def add_field(var: Var, is_initialized_in_class: bool = False, add_field(Var('_source', strtype), is_initialized_in_class=True) # TODO: SelfType should be bind to actual 'self' - this_type = self_type(info) + this_type = fill_typevars(info) def add_method(funcname: str, ret: Type, args: List[Argument], name=None, is_classmethod=False) -> None: @@ -2885,7 +2885,7 @@ def builtin_type(self, name: str, args: List[Type] = None) -> Instance: return Instance(sym.node, args or []) -def self_type(typ: TypeInfo) -> Union[Instance, TupleType]: +def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: """For a non-generic type, return instance type representing the type. For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn]. """ diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c70f3d5417b6..57fd91722b95 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2103,7 +2103,7 @@ reveal_type(B.new()) # E: Revealed type is '__main__.B' [builtins fixtures/classmethod.pyi] [case testSelfTypeOverride] -from typing import TypeVar +from typing import TypeVar, cast T = TypeVar('T') @@ -2120,3 +2120,18 @@ reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' reveal_type(C().copy()) # E: Revealed type is '__main__.C' reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' + +[case testSelfTypeSuper] +from typing import TypeVar, cast + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + def copy(self: T) -> T: + reveal_type(super().copy()) # E: Revealed type is '__main__.B' + cast(T, B()) +[out] +main: note: In member "copy" of class "B": \ No newline at end of file From 4d5d479ebb8557018c0cbd30c720252977c93384 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 06:21:58 +0300 Subject: [PATCH 07/47] more tests --- test-data/unit/check-classes.test | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 57fd91722b95..d061cc214a04 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2134,4 +2134,23 @@ class B(A): reveal_type(super().copy()) # E: Revealed type is '__main__.B' cast(T, B()) [out] -main: note: In member "copy" of class "B": \ No newline at end of file +main: note: In member "copy" of class "B": + +[case testSelfTypeCallable] +from typing import TypeVar, cast, Callable + +T = TypeVar('T', 'A') +-- FIX: make this bound implicit + +class A: + def copy(self: T, factory: Callable[['A'], T]) -> T: + # This seems to work only by accident + return factory(self) + +Q = TypeVar('Q', 'B') +class B(A): + def copy(self: Q, factory: Callable[['B'], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" + return factory(self) + +[out] +main: note: In class "B": From ea6c4851108f4ed554ae136fabf9ccf627d8e3d7 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 06:24:40 +0300 Subject: [PATCH 08/47] unneeded changes --- mypy/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index dcb49af1e63b..99fc06a50e58 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1630,9 +1630,9 @@ def accept(self, visitor: NodeVisitor[T]) -> T: # # If T is contravariant in Foo[T], Foo[object] is a subtype of # Foo[int], but not vice versa. -CONTRAVARIANT = -1 # type: int INVARIANT = 0 # type: int COVARIANT = 1 # type: int +CONTRAVARIANT = 2 # type: int class TypeVarExpr(SymbolNode, Expression): From 00596a6da14aa7e5cb188d083d9de1656a5f4376 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 06:37:39 +0300 Subject: [PATCH 09/47] merge --- mypy/typeanal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 557691b7f014..0fe5c451f2b2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -107,8 +107,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: elif fullname == 'typing.Any': return AnyType() elif fullname == 'typing.Tuple': - if len(t.args) == 0: - t.args = [AnyType(), EllipsisType()] if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) node = self.lookup_fqn_func('builtins.tuple') From 39615c23c5168c783b694daa32cbc80c1355bbab Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 14:18:58 +0300 Subject: [PATCH 10/47] some more tests --- test-data/unit/check-classes.test | 37 ++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d061cc214a04..e030d8b0d6ca 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2082,6 +2082,30 @@ reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' reveal_type(A().copy()) # E: Revealed type is '__main__.A' reveal_type(B().copy()) # E: Revealed type is '__main__.B' + +[case testSelfTypeReturn] +from typing import TypeVar, Type + +T = TypeVar('T') + +def _type(self: T) -> Type[T]: ... + +class A: + def copy(self: T) -> T: + if B(): + return A() + elif A(): + return B() + return _type(self)() + +class B(A): + pass + +[out] +main: note: In member "copy" of class "A": +main:10: error: Incompatible return value type (got "A", expected "T") +main:12: error: Incompatible return value type (got "B", expected "T") + [case testSelfTypeClass] from typing import TypeVar, Type @@ -2131,8 +2155,12 @@ class A: class B(A): def copy(self: T) -> T: - reveal_type(super().copy()) # E: Revealed type is '__main__.B' - cast(T, B()) + reveal_type(self) # E: Revealed type is 'T`-1' + # TODO: + # reveal_type(super().copy()) # Revealed type is 'T`-1' + # return super().copy() + return cast(T, super().copy()) + [out] main: note: In member "copy" of class "B": @@ -2143,13 +2171,12 @@ T = TypeVar('T', 'A') -- FIX: make this bound implicit class A: - def copy(self: T, factory: Callable[['A'], T]) -> T: - # This seems to work only by accident + def copy(self: T, factory: Callable[[T], T]) -> T: return factory(self) Q = TypeVar('Q', 'B') class B(A): - def copy(self: Q, factory: Callable[['B'], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" + def copy(self: Q, factory: Callable[[Q], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" return factory(self) [out] From f8875f9e2bbfc6ae5b25116a69f5c82ac2d09c6a Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 29 Sep 2016 22:53:35 +0300 Subject: [PATCH 11/47] recursive instantiation --- mypy/types.py | 26 +++++++++++++++++++------- test-data/unit/check-classes.test | 30 ++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index a6ba0baae80f..b153a5a66f79 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -682,7 +682,7 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': is_classmethod_class=data['is_classmethod_class'], ) - def bind_self(self, self_type: Type = None) -> 'CallableType': + def bind_self(self, actual_self: Type = None) -> 'CallableType': if self.arg_kinds and self.arg_kinds[0] == mypy.nodes.ARG_STAR: # The signature is of the form 'def foo(*args, ...)'. # In this case we shouldn't drop the first arg, @@ -690,17 +690,18 @@ def bind_self(self, self_type: Type = None) -> 'CallableType': return self ret_type = self.ret_type variables = list(self.variables) - if self.arg_types and self_type is not None: + arg_types = self.arg_types[1:] + if self.arg_types and actual_self is not None: self_arg = self.arg_types[0] if isinstance(self_arg, TypeType): - print(self_arg) self_arg = self_arg.item - print(self_arg) if isinstance(self_arg, TypeVarType): + instantiator = TypeVarInstantiator(self_arg.id, actual_self) + arg_types = [t.accept(instantiator) for t in arg_types] + ret_type = ret_type.accept(instantiator) variables = variables[1:] - if isinstance(ret_type, TypeVarType) and self_arg.name == ret_type.name: - ret_type = self_type - return self.copy_modified(arg_types=self.arg_types[1:], + + return self.copy_modified(arg_types=arg_types, arg_kinds=self.arg_kinds[1:], arg_names=self.arg_names[1:], variables=variables, @@ -1534,3 +1535,14 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike name, implicit=True, ) + + +class TypeVarInstantiator(TypeTranslator): + def __init__(self, formal: TypeVarId, actual: Type) -> None: + self.formal = formal + self.actual = actual + + def visit_type_var(self, t: TypeVarType) -> Type: + if t.id == self.formal: + return self.actual + return t diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e030d8b0d6ca..b5f2a650935d 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2164,8 +2164,30 @@ class B(A): [out] main: note: In member "copy" of class "B": +[case testSelfTypeRecursiveBinding] +from typing import TypeVar, Callable, Type + +T = TypeVar('T') +class A: + def copy(self: T, factory: Callable[[T], T]) -> T: + return factory(self) + + @classmethod + def new(cls: Type[T], factory: Callable[[T], T]) -> T: + return cls() + +class B(A): + pass + +reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' +reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' + +[builtins fixtures/classmethod.pyi] + [case testSelfTypeCallable] -from typing import TypeVar, cast, Callable +from typing import TypeVar, Callable T = TypeVar('T', 'A') -- FIX: make this bound implicit @@ -2176,8 +2198,8 @@ class A: Q = TypeVar('Q', 'B') class B(A): - def copy(self: Q, factory: Callable[[Q], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" - return factory(self) - + def copy(self: Q, factory: Callable[[Q], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" + return factory(self) + [out] main: note: In class "B": From 9ae3bf9451052e0d4252bdaff274071456fc18b5 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 30 Sep 2016 03:15:49 +0300 Subject: [PATCH 12/47] add implicit bound --- mypy/checker.py | 7 ++++--- mypy/nodes.py | 3 ++- mypy/semanal.py | 19 +++++++++++------- mypy/typeanal.py | 6 +++++- mypy/types.py | 4 +++- test-data/unit/check-classes.test | 32 +++++++++++++++++++------------ 6 files changed, 46 insertions(+), 25 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0bdf183fe747..c34f9e057440 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -27,8 +27,7 @@ DictionaryComprehension, ComplexExpr, EllipsisExpr, TypeAliasExpr, RefExpr, YieldExpr, BackquoteExpr, ImportFrom, ImportAll, ImportBase, AwaitExpr, - CONTRAVARIANT, COVARIANT -) + CONTRAVARIANT, COVARIANT) from mypy import nodes from mypy.types import ( Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, @@ -591,7 +590,9 @@ def is_implicit_any(t: Type) -> bool: if arg_type.variance == COVARIANT: self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, arg_type) - + # TODO: + # if arg_type.variance == SELF_VARIANCE and i > 0: + # self.fail("Cannot use bare self type in method arguments", arg_type) if typ.arg_kinds[i] == nodes.ARG_STAR: # builtins.tuple[T] is typing.Tuple[T, ...] arg_type = self.named_generic_type('builtins.tuple', diff --git a/mypy/nodes.py b/mypy/nodes.py index 99fc06a50e58..a1e93c1fced1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1630,9 +1630,10 @@ def accept(self, visitor: NodeVisitor[T]) -> T: # # If T is contravariant in Foo[T], Foo[object] is a subtype of # Foo[int], but not vice versa. +CONTRAVARIANT = -1 # type: int INVARIANT = 0 # type: int COVARIANT = 1 # type: int -CONTRAVARIANT = 2 # type: int +# TODO: SELF_VARIANCE = 2 # type: int class TypeVarExpr(SymbolNode, Expression): diff --git a/mypy/semanal.py b/mypy/semanal.py index b385b1b07dd3..5845411c799b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -323,16 +323,21 @@ def visit_func_def(self, defn: FuncDef) -> None: def prepare_method_signature(self, func: FuncDef) -> None: """Check basic signature validity and tweak annotation of self/cls argument.""" # Only non-static methods are special. + functype = func.type if not func.is_static: if not func.arguments: self.fail('Method must have at least one argument', func) - elif func.type and func.arguments[0].type_annotation is None: - if func.is_class: - leading_type = self.class_type(self.type) - else: - leading_type = fill_typevars(self.type) - sig = cast(FunctionLike, func.type) - func.type = replace_implicit_first_type(sig, leading_type) + elif isinstance(functype, CallableType): + self_type = functype.arg_types[0] + if isinstance(self_type, AnyType): + if func.is_class: + leading_type = self.class_type(self.type) + else: + leading_type = fill_typevars(self.type) + sig = cast(FunctionLike, func.type) + func.type = replace_implicit_first_type(sig, leading_type) + elif isinstance(self_type, UnboundType): + self_type.enclosing_type = fill_typevars(self.type) def is_conditional_func(self, previous: Node, new: FuncDef) -> bool: """Does 'new' conditionally redefine 'previous'? diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0fe5c451f2b2..9c25ebca4e6d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -98,7 +98,11 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Type variable "{}" used with arguments'.format( t.name), t) assert sym.tvar_def is not None - return TypeVarType(sym.tvar_def, t.line) + ret = TypeVarType(sym.tvar_def, t.line) + if t.enclosing_type: + # TODO: ret.variance = nodes.SELF_VARIANCE + ret.upper_bound = t.enclosing_type + return ret elif fullname == 'builtins.None': if experiments.STRICT_OPTIONAL: return NoneTyp(is_ret_type=t.is_ret_type) diff --git a/mypy/types.py b/mypy/types.py index b153a5a66f79..b0fb6c0be97d 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -7,7 +7,7 @@ ) import mypy.nodes -from mypy.nodes import INVARIANT, COVARIANT, SymbolNode +from mypy.nodes import INVARIANT, SymbolNode from mypy import experiments @@ -170,6 +170,8 @@ class UnboundType(Type): optional = False # is this type a return type? is_ret_type = False + # is this the type of the self parameter in a method? + enclosing_type = None # type: Type def __init__(self, name: str, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b5f2a650935d..1f538eaf7268 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2081,6 +2081,9 @@ reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' reveal_type(A().copy()) # E: Revealed type is '__main__.A' reveal_type(B().copy()) # E: Revealed type is '__main__.B' +# FIX: Why partial type? +# reveal_type(A.copy(A())) +# reveal_type(B.copy(B())) [case testSelfTypeReturn] @@ -2156,7 +2159,8 @@ class A: class B(A): def copy(self: T) -> T: reveal_type(self) # E: Revealed type is 'T`-1' - # TODO: + ## TODO: + ## super() dispatch is A.copy, but the type binding should be T=B # reveal_type(super().copy()) # Revealed type is 'T`-1' # return super().copy() return cast(T, super().copy()) @@ -2186,20 +2190,24 @@ reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __ma [builtins fixtures/classmethod.pyi] -[case testSelfTypeCallable] -from typing import TypeVar, Callable +[case testSelfTypeBound] +from typing import TypeVar, Callable, cast -T = TypeVar('T', 'A') --- FIX: make this bound implicit +T = TypeVar('T') class A: - def copy(self: T, factory: Callable[[T], T]) -> T: - return factory(self) + def copy(self: T) -> T: + ... -Q = TypeVar('Q', 'B') class B(A): - def copy(self: Q, factory: Callable[[Q], Q]) -> Q: # E: Signature of "copy" incompatible with supertype "A" - return factory(self) - + x = 1 + def copy(self: T) -> T: + reveal_type(self.x) # E: Revealed type is 'builtins.int' + return cast(T, None) [out] -main: note: In class "B": +main: note: In member "copy" of class "B": + +-- TODO: fail for this +-- [case testSelfTypeBare] +-- class E: +-- def copy(self: T, other: T) -> T: ... From b29bf87b4bca940e8f6e91c75987c1bf570a9a5a Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 30 Sep 2016 03:23:50 +0300 Subject: [PATCH 13/47] add tests: prohibit overriding without selftype --- mypy/typeanal.py | 1 + test-data/unit/check-classes.test | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9c25ebca4e6d..50e153eb27d0 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -101,6 +101,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: ret = TypeVarType(sym.tvar_def, t.line) if t.enclosing_type: # TODO: ret.variance = nodes.SELF_VARIANCE + ret.variance = nodes.INVARIANT ret.upper_bound = t.enclosing_type return ret elif fullname == 'builtins.None': diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1f538eaf7268..97ee33e84c8e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2199,12 +2199,30 @@ class A: def copy(self: T) -> T: ... +class C(A): + def copy(self: C) -> C: + ... + +class D(A): + def copy(self: A) -> A: + ... + +class E(A): + def copy(self: A) -> E: + ... + class B(A): x = 1 def copy(self: T) -> T: reveal_type(self.x) # E: Revealed type is 'builtins.int' return cast(T, None) [out] +main: note: In class "C": +main:10: error: Return type of "copy" incompatible with supertype "A" +main: note: In class "D": +main:14: error: Return type of "copy" incompatible with supertype "A" +main: note: In class "E": +main:18: error: Return type of "copy" incompatible with supertype "A" main: note: In member "copy" of class "B": -- TODO: fail for this From 9b68a2f05da17ee396c85cd387594bc5f22f6853 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 30 Sep 2016 04:16:10 +0300 Subject: [PATCH 14/47] minor --- mypy/checkmember.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 4fc8f88da6c4..249a9ee1e02d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -65,8 +65,8 @@ def analyze_member_access(name: str, msg.cant_assign_to_method(node) signature = function_type(method, builtin_type('builtins.function')) if name == '__new__': - # __new__ is special and behaves like a static method - # -- don't strip the first argument. + # __new__ is special and behaves like a static method -- don't strip + # the first argument. pass else: signature = signature.bind_self(typ) From 6c161b9c562d4ebf50eb3e6659c7e05015520565 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 30 Sep 2016 12:10:42 +0300 Subject: [PATCH 15/47] fix comment --- mypy/types.py | 2 +- typeshed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index efbda2fddc27..9047622c8999 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -182,7 +182,7 @@ class UnboundType(Type): # is this type a return type? is_ret_type = False - # is this the type of the self parameter in a method? + # If it's annotation for `self` parameter, the type of the enclosing class enclosing_type = None # type: Type # special case for X[()] diff --git a/typeshed b/typeshed index 1d820d48cfb1..aa549db5e5e5 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 1d820d48cfb16e78a2858d7687f129c13ff2111f +Subproject commit aa549db5e5e57ee2702899d1cc660163b52171ed From 1cae0341ecb4d2b81c5e56c9ae892e5271760a05 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sat, 1 Oct 2016 23:59:38 +0300 Subject: [PATCH 16/47] rename: self->cls --- mypy/nodes.py | 2 +- mypy/types.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 41875cecb25e..1ab9e7b633d6 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -128,7 +128,7 @@ def get_column(self) -> int: return self.column def accept(self, visitor: NodeVisitor[T]) -> T: - raise RuntimeError('Not implemented: {}.accept({})'.format(type(self), type(visitor))) + raise RuntimeError('Not implemented') # These are placeholders for a future refactoring; see #1783. diff --git a/mypy/types.py b/mypy/types.py index 9047622c8999..2f3a878b366f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -215,7 +215,7 @@ def serialize(self) -> JsonDict: } @classmethod - def deserialize(self, data: JsonDict) -> 'UnboundType': + def deserialize(cls, data: JsonDict) -> 'UnboundType': assert data['.class'] == 'UnboundType' return UnboundType(data['name'], [Type.deserialize(a) for a in data['args']]) @@ -251,7 +251,7 @@ def serialize(self) -> JsonDict: } @classmethod - def deserialize(self, data: JsonDict) -> 'TypeList': + def deserialize(cls, data: JsonDict) -> 'TypeList': assert data['.class'] == 'TypeList' return TypeList([Type.deserialize(t) for t in data['items']]) @@ -369,7 +369,7 @@ def serialize(self) -> JsonDict: } @classmethod - def deserialize(self, data: JsonDict) -> 'NoneTyp': + def deserialize(cls, data: JsonDict) -> 'NoneTyp': assert data['.class'] == 'NoneTyp' return NoneTyp(is_ret_type=data['is_ret_type']) @@ -405,7 +405,7 @@ def serialize(self) -> JsonDict: 'source': self.source} @classmethod - def deserialize(self, data: JsonDict) -> 'DeletedType': + def deserialize(cls, data: JsonDict) -> 'DeletedType': assert data['.class'] == 'DeletedType' return DeletedType(data['source']) @@ -778,7 +778,7 @@ def serialize(self) -> JsonDict: } @classmethod - def deserialize(self, data: JsonDict) -> 'Overloaded': + def deserialize(cls, data: JsonDict) -> 'Overloaded': assert data['.class'] == 'Overloaded' return Overloaded([CallableType.deserialize(t) for t in data['items']]) @@ -990,7 +990,7 @@ def serialize(self) -> JsonDict: return {'.class': 'EllipsisType'} @classmethod - def deserialize(self, data: JsonDict) -> 'EllipsisType': + def deserialize(cls, data: JsonDict) -> 'EllipsisType': assert data['.class'] == 'EllipsisType' return EllipsisType() From 733edc1807ea383fdd602dce1240cdca5548e920 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 5 Oct 2016 02:41:03 +0300 Subject: [PATCH 17/47] Fix tests, separate binding from the classes --- mypy/checker.py | 12 +++--- mypy/checkmember.py | 12 +++--- mypy/nodes.py | 3 +- mypy/typeanal.py | 6 ++- mypy/types.py | 67 +++++++++++++++++-------------- test-data/unit/check-classes.test | 58 ++++++++++++++++---------- 6 files changed, 90 insertions(+), 68 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ef2c1eb89e3d..0a1e79a42749 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -33,7 +33,7 @@ Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, - true_only, false_only, function_type + true_only, false_only, function_type, bind_self ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -747,7 +747,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: method = defn.name() if method not in nodes.inplace_operator_methods: return - typ = self.function_type(defn).bind_self() + typ = bind_self(self.function_type(defn)) cls = defn.info other_method = '__' + method[3:] if cls.has_readable_member(other_method): @@ -827,7 +827,7 @@ def check_method_override_for_base_with_name( # The name of the method is defined in the base class. # Construct the type of the overriding method. - typ = self.function_type(defn).bind_self() + typ = bind_self(self.function_type(defn)) # Map the overridden method type to subtype context so that # it can be checked for compatibility. original_type = base_attr.type @@ -840,7 +840,7 @@ def check_method_override_for_base_with_name( assert False, str(base_attr.node) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( - original_type.bind_self(), + bind_self(original_type), defn.info, base) # Check that the types are compatible. # TODO overloaded signatures @@ -979,8 +979,8 @@ def check_compatibility(self, name: str, base1: TypeInfo, if (isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike)): # Method override - first_sig = first_type.bind_self() - second_sig = second_type.bind_self() + first_sig = bind_self(first_type) + second_sig = bind_self(second_type) ok = is_subtype(first_sig, second_sig) elif first_type and second_type: ok = is_equivalent(first_type, second_type) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 249a9ee1e02d..27f5207ba362 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarDef, Overloaded, TypeVarType, UnionType, PartialType, - DeletedType, NoneTyp, TypeType, function_type + DeletedType, NoneTyp, TypeType, function_type, bind_self ) from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2 @@ -69,7 +69,7 @@ def analyze_member_access(name: str, # the first argument. pass else: - signature = signature.bind_self(typ) + signature = bind_self(signature, typ) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: @@ -196,7 +196,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: function = function_type(method, builtin_type('builtins.function')) - bound_method = function.bind_self(itype) + bound_method = bind_self(function, itype) typ = map_instance_to_supertype(itype, method.info) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): @@ -246,7 +246,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = functype.bind_self() + signature = bind_self(functype) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -371,7 +371,7 @@ def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = t.bind_self(itype) + t = bind_self(t, itype) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, @@ -419,7 +419,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo, fallback: Instance) -> FunctionLike: - signature = function_type(init_or_new, fallback).bind_self() + signature = bind_self(function_type(init_or_new, fallback)) # The __init__ method might come from a generic superclass # (init_or_new.info) with type variables that do not map diff --git a/mypy/nodes.py b/mypy/nodes.py index 1ab9e7b633d6..c1274ac8c122 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1647,10 +1647,9 @@ def accept(self, visitor: NodeVisitor[T]) -> T: # # If T is contravariant in Foo[T], Foo[object] is a subtype of # Foo[int], but not vice versa. -CONTRAVARIANT = -1 # type: int INVARIANT = 0 # type: int COVARIANT = 1 # type: int -# TODO: SELF_VARIANCE = 2 # type: int +CONTRAVARIANT = 2 # type: int class TypeVarExpr(SymbolNode, Expression): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3a2f1cf7fdbb..f5886c96d797 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -100,7 +100,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: assert sym.tvar_def is not None ret = TypeVarType(sym.tvar_def, t.line) if t.enclosing_type: - # TODO: ret.variance = nodes.SELF_VARIANCE + # SelfType ret.variance = nodes.INVARIANT ret.upper_bound = t.enclosing_type return ret @@ -144,6 +144,10 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Type[...] must have exactly one type argument', t) items = self.anal_array(t.args) item = items[0] + if t.enclosing_type and isinstance(item, TypeVarType): + # SelfType + item.variance = nodes.INVARIANT + item.upper_bound = t.enclosing_type return TypeType(item, line=t.line) elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. diff --git a/mypy/types.py b/mypy/types.py index 2f3a878b366f..ddb065090cf5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -533,9 +533,6 @@ def with_name(self, name: str) -> 'FunctionLike': pass def deserialize(cls, data: JsonDict) -> 'FunctionLike': return cast(FunctionLike, super().deserialize(data)) - @abstractmethod - def bind_self(self, self_type=None) -> 'FunctionLike': pass - _dummy = object() # type: Any @@ -705,31 +702,6 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': is_classmethod_class=data['is_classmethod_class'], ) - def bind_self(self, actual_self: Type = None) -> 'CallableType': - if self.arg_kinds and self.arg_kinds[0] == mypy.nodes.ARG_STAR: - # The signature is of the form 'def foo(*args, ...)'. - # In this case we shouldn't drop the first arg, - # since self will be absorbed by the *args. - return self - ret_type = self.ret_type - variables = list(self.variables) - arg_types = self.arg_types[1:] - if self.arg_types and actual_self is not None: - self_arg = self.arg_types[0] - if isinstance(self_arg, TypeType): - self_arg = self_arg.item - if isinstance(self_arg, TypeVarType): - instantiator = TypeVarInstantiator(self_arg.id, actual_self) - arg_types = [t.accept(instantiator) for t in arg_types] - ret_type = ret_type.accept(instantiator) - variables = variables[1:] - - return self.copy_modified(arg_types=arg_types, - arg_kinds=self.arg_kinds[1:], - arg_names=self.arg_names[1:], - variables=variables, - ret_type=ret_type) - class Overloaded(FunctionLike): """Overloaded function type T1, ... Tn, where each Ti is CallableType. @@ -782,9 +754,6 @@ def deserialize(cls, data: JsonDict) -> 'Overloaded': assert data['.class'] == 'Overloaded' return Overloaded([CallableType.deserialize(t) for t in data['items']]) - def bind_self(self, self_type: Type = None) -> 'FunctionLike': - return Overloaded([c.bind_self(self_type) for c in self.items()]) - class TupleType(Type): """The tuple type Tuple[T1, ..., Tn] (at least one type argument). @@ -1560,6 +1529,42 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike ) +F = TypeVar('F', bound=FunctionLike) + + +def bind_self(t: F, actual_self: Type = None) -> F: + if isinstance(t, Overloaded): + return cast(F, Overloaded([bind_self(c, t) for c in t.items()])) + assert isinstance(t, CallableType) + if t.arg_kinds and t.arg_kinds[0] == mypy.nodes.ARG_STAR: + # The signature is of the form 'def foo(*args, ...)'. + # In this case we shouldn't drop the first arg, + # since t will be absorbed by the *args. + return cast(F, t) + ret_type = t.ret_type + variables = list(t.variables) + arg_types = t.arg_types[1:] + if t.arg_types and actual_self is not None: + self_arg = t.arg_types[0] + if isinstance(self_arg, TypeType): + self_arg = self_arg.item + if isinstance(self_arg, TypeVarType): + instantiator = TypeVarInstantiator(self_arg.id, actual_self) + arg_types = [t.accept(instantiator) for t in arg_types] + # from mypy.expandtype import expand_type + # arg_types = [expand_type(t, {self_arg.id: actual_self}) + # for t in arg_types] + ret_type = ret_type.accept(instantiator) + variables = variables[1:] + + res = t.copy_modified(arg_types=arg_types, + arg_kinds=t.arg_kinds[1:], + arg_names=t.arg_names[1:], + variables=variables, + ret_type=ret_type) + return cast(F, res) + + class TypeVarInstantiator(TypeTranslator): def __init__(self, formal: TypeVarId, actual: Type) -> None: self.formal = formal diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index aad256ecb179..b6a90a7b795d 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2087,6 +2087,7 @@ reveal_type(B().copy()) # E: Revealed type is '__main__.B' [case testSelfTypeReturn] +# flags: --hide-error-context from typing import TypeVar, Type T = TypeVar('T') @@ -2096,20 +2097,27 @@ def _type(self: T) -> Type[T]: ... class A: def copy(self: T) -> T: if B(): - return A() + return A() # E: Incompatible return value type (got "A", expected "T") elif A(): - return B() + return B() # E: Incompatible return value type (got "B", expected "T") return _type(self)() class B(A): pass -[out] -main: note: In member "copy" of class "A": -main:10: error: Incompatible return value type (got "A", expected "T") -main:12: error: Incompatible return value type (got "B", expected "T") +class C: + def __init__(self, a: int) -> None: pass + + def copy(self: T) -> T: + if self: + return _type(self)(1) + else: + return _type(self)() # E: Too few arguments for "C" + + [case testSelfTypeClass] +# flags: --hide-error-context from typing import TypeVar, Type T = TypeVar('T') @@ -2122,6 +2130,17 @@ class A: class B(A): pass +class C: + def __init__(self, a: int) -> None: pass + + @classmethod + def new(cls: Type[T]) -> T: + if cls: + return cls(1) + else: + return cls() # E: Too few arguments for "C" + + reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A' reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B' reveal_type(A.new()) # E: Revealed type is '__main__.A' @@ -2149,6 +2168,7 @@ reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' [case testSelfTypeSuper] +# flags: --hide-error-context from typing import TypeVar, cast T = TypeVar('T') @@ -2165,10 +2185,8 @@ class B(A): # return super().copy() return cast(T, super().copy()) -[out] -main: note: In member "copy" of class "B": - [case testSelfTypeRecursiveBinding] +# flags: --hide-error-context from typing import TypeVar, Callable, Type T = TypeVar('T') @@ -2178,6 +2196,9 @@ class A: @classmethod def new(cls: Type[T], factory: Callable[[T], T]) -> T: + reveal_type(cls) # E: Revealed type is 'Type[T`-1]' + reveal_type(cls()) # E: Revealed type is 'T`-1' + cls(2) # E: Too many arguments for "A" return cls() class B(A): @@ -2191,6 +2212,7 @@ reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __ma [builtins fixtures/classmethod.pyi] [case testSelfTypeBound] +# flags: --hide-error-context from typing import TypeVar, Callable, cast T = TypeVar('T') @@ -2200,15 +2222,15 @@ class A: ... class C(A): - def copy(self: C) -> C: + def copy(self: C) -> C: # E: Return type of "copy" incompatible with supertype "A" ... class D(A): - def copy(self: A) -> A: + def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" ... class E(A): - def copy(self: A) -> E: + def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" ... class B(A): @@ -2216,16 +2238,8 @@ class B(A): def copy(self: T) -> T: reveal_type(self.x) # E: Revealed type is 'builtins.int' return cast(T, None) -[out] -main: note: In class "C": -main:10: error: Return type of "copy" incompatible with supertype "A" -main: note: In class "D": -main:14: error: Return type of "copy" incompatible with supertype "A" -main: note: In class "E": -main:18: error: Return type of "copy" incompatible with supertype "A" -main: note: In member "copy" of class "B": - --- TODO: fail for this + +-- # TODO: fail for this -- [case testSelfTypeBare] -- class E: -- def copy(self: T, other: T) -> T: ... From abcb094f0ef48444a166984b56496d4ff4b68c75 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 5 Oct 2016 17:44:29 +0300 Subject: [PATCH 18/47] add Guido's test --- mypy/types.py | 7 ++++--- test-data/unit/check-classes.test | 14 ++++++++++++++ typeshed | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 66d99b56c736..4f4fbff3bd45 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1551,10 +1551,11 @@ def bind_self(t: F, actual_self: Type = None) -> F: if isinstance(self_arg, TypeVarType): instantiator = TypeVarInstantiator(self_arg.id, actual_self) arg_types = [t.accept(instantiator) for t in arg_types] - # from mypy.expandtype import expand_type - # arg_types = [expand_type(t, {self_arg.id: actual_self}) - # for t in arg_types] ret_type = ret_type.accept(instantiator) + #from mypy.expandtype import expand_type + #arg_types = [expand_type(t, {self_arg.id: actual_self}) + # for t in arg_types] + #ret_type = expand_type(ret_type, {self_arg.id: actual_self}) variables = variables[1:] res = t.copy_modified(arg_types=arg_types, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b6a90a7b795d..2ca111f7a949 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2243,3 +2243,17 @@ class B(A): -- [case testSelfTypeBare] -- class E: -- def copy(self: T, other: T) -> T: ... + +[case testSelfTypeClone] +from typing import TypeVar + +T = TypeVar('T', bound='C') + +class C: + def copy(self: T) -> T: + return self + +def clone(arg: T) -> T: + reveal_type(arg) + reveal_type(arg.copy) # E: Revealed type is 'def () -> __main__.C' + return arg.copy() diff --git a/typeshed b/typeshed index 0e5003b61e7c..aa549db5e5e5 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 0e5003b61e7c0aba8f158f72fe1827fd6dcd4673 +Subproject commit aa549db5e5e57ee2702899d1cc660163b52171ed From 9f831c41c4fb8b78b04814bba8bb6d0db07159d1 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 14:31:25 +0300 Subject: [PATCH 19/47] move tests to dedicated file --- mypy/test/testcheck.py | 1 + test-data/unit/check-classes.test | 192 ------------------------------ typeshed | 2 +- 3 files changed, 2 insertions(+), 193 deletions(-) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 209076ecddf2..31c6eca02bc2 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -67,6 +67,7 @@ 'check-async-await.test', 'check-newtype.test', 'check-columns.test', + 'check-selftype.test', ] if 'annotation' in typed_ast.ast35.Assign._fields: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 2ca111f7a949..5792c7c966a6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2065,195 +2065,3 @@ class A: pass class B(object, A): # E: Cannot determine consistent method resolution order (MRO) for "B" def readlines(self): pass __iter__ = readlines - -[case testSelfTypeInstance] -from typing import TypeVar - -T = TypeVar('T') - -class A: - def copy(self: T) -> T: ... - -class B(A): - pass - -reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' -reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' -reveal_type(A().copy()) # E: Revealed type is '__main__.A' -reveal_type(B().copy()) # E: Revealed type is '__main__.B' -# FIX: Why partial type? -# reveal_type(A.copy(A())) -# reveal_type(B.copy(B())) - - -[case testSelfTypeReturn] -# flags: --hide-error-context -from typing import TypeVar, Type - -T = TypeVar('T') - -def _type(self: T) -> Type[T]: ... - -class A: - def copy(self: T) -> T: - if B(): - return A() # E: Incompatible return value type (got "A", expected "T") - elif A(): - return B() # E: Incompatible return value type (got "B", expected "T") - return _type(self)() - -class B(A): - pass - -class C: - def __init__(self, a: int) -> None: pass - - def copy(self: T) -> T: - if self: - return _type(self)(1) - else: - return _type(self)() # E: Too few arguments for "C" - - - -[case testSelfTypeClass] -# flags: --hide-error-context -from typing import TypeVar, Type - -T = TypeVar('T') - -class A: - @classmethod - def new(cls: Type[T]) -> T: - return cls() - -class B(A): - pass - -class C: - def __init__(self, a: int) -> None: pass - - @classmethod - def new(cls: Type[T]) -> T: - if cls: - return cls(1) - else: - return cls() # E: Too few arguments for "C" - - -reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A' -reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B' -reveal_type(A.new()) # E: Revealed type is '__main__.A' -reveal_type(B.new()) # E: Revealed type is '__main__.B' - -[builtins fixtures/classmethod.pyi] - -[case testSelfTypeOverride] -from typing import TypeVar, cast - -T = TypeVar('T') - -class A: - def copy(self: T) -> T: ... - -class B(A): - pass - -class C(A): - def copy(self: T) -> T: ... - -reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' -reveal_type(C().copy()) # E: Revealed type is '__main__.C' -reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' -reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' - -[case testSelfTypeSuper] -# flags: --hide-error-context -from typing import TypeVar, cast - -T = TypeVar('T') - -class A: - def copy(self: T) -> T: ... - -class B(A): - def copy(self: T) -> T: - reveal_type(self) # E: Revealed type is 'T`-1' - ## TODO: - ## super() dispatch is A.copy, but the type binding should be T=B - # reveal_type(super().copy()) # Revealed type is 'T`-1' - # return super().copy() - return cast(T, super().copy()) - -[case testSelfTypeRecursiveBinding] -# flags: --hide-error-context -from typing import TypeVar, Callable, Type - -T = TypeVar('T') -class A: - def copy(self: T, factory: Callable[[T], T]) -> T: - return factory(self) - - @classmethod - def new(cls: Type[T], factory: Callable[[T], T]) -> T: - reveal_type(cls) # E: Revealed type is 'Type[T`-1]' - reveal_type(cls()) # E: Revealed type is 'T`-1' - cls(2) # E: Too many arguments for "A" - return cls() - -class B(A): - pass - -reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' -reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' - -[builtins fixtures/classmethod.pyi] - -[case testSelfTypeBound] -# flags: --hide-error-context -from typing import TypeVar, Callable, cast - -T = TypeVar('T') - -class A: - def copy(self: T) -> T: - ... - -class C(A): - def copy(self: C) -> C: # E: Return type of "copy" incompatible with supertype "A" - ... - -class D(A): - def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" - ... - -class E(A): - def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" - ... - -class B(A): - x = 1 - def copy(self: T) -> T: - reveal_type(self.x) # E: Revealed type is 'builtins.int' - return cast(T, None) - --- # TODO: fail for this --- [case testSelfTypeBare] --- class E: --- def copy(self: T, other: T) -> T: ... - -[case testSelfTypeClone] -from typing import TypeVar - -T = TypeVar('T', bound='C') - -class C: - def copy(self: T) -> T: - return self - -def clone(arg: T) -> T: - reveal_type(arg) - reveal_type(arg.copy) # E: Revealed type is 'def () -> __main__.C' - return arg.copy() diff --git a/typeshed b/typeshed index aa549db5e5e5..f90a6d1c0eed 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit aa549db5e5e57ee2702899d1cc660163b52171ed +Subproject commit f90a6d1c0eeda7b2b267d9bbfcc6681f4d9f8168 From aade1ed3e2db8c70a99e7a5bc93c43cac7afc4bd Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 14:35:45 +0300 Subject: [PATCH 20/47] move tests to dedicated file --- mypy/checkexpr.py | 1 + mypy/checkmember.py | 1 + mypy/types.py | 1 + 3 files changed, 3 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 03bc748afa3d..43369d4503dc 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -906,6 +906,7 @@ def analyze_ordinary_member_access(self, e: MemberExpr, return self.analyze_ref_expr(e) else: # This is a reference to a non-module attribute. + import pdb; pdb.set_trace() return analyze_member_access(e.name, self.accept(e.expr), e, is_lvalue, False, False, self.named_type, self.not_ready_callback, self.msg, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 27f5207ba362..9de4e911ae90 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -69,6 +69,7 @@ def analyze_member_access(name: str, # the first argument. pass else: + assert not typ signature = bind_self(signature, typ) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) diff --git a/mypy/types.py b/mypy/types.py index 4f4fbff3bd45..66490dda44dc 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1552,6 +1552,7 @@ def bind_self(t: F, actual_self: Type = None) -> F: instantiator = TypeVarInstantiator(self_arg.id, actual_self) arg_types = [t.accept(instantiator) for t in arg_types] ret_type = ret_type.accept(instantiator) + #FIX: why is this partial? #from mypy.expandtype import expand_type #arg_types = [expand_type(t, {self_arg.id: actual_self}) # for t in arg_types] From 8e81051f64652936e5d0d4a3a40aaf4643d2f3b1 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 15:11:17 +0300 Subject: [PATCH 21/47] pass report_type to instantiate --- mypy/checkexpr.py | 1 - mypy/checkmember.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c83296443535..3d322c46ce9d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -906,7 +906,6 @@ def analyze_ordinary_member_access(self, e: MemberExpr, return self.analyze_ref_expr(e) else: # This is a reference to a non-module attribute. - import pdb; pdb.set_trace() return analyze_member_access(e.name, self.accept(e.expr), e, is_lvalue, False, False, self.named_type, self.not_ready_callback, self.msg, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 9de4e911ae90..01177844057a 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -69,8 +69,7 @@ def analyze_member_access(name: str, # the first argument. pass else: - assert not typ - signature = bind_self(signature, typ) + signature = bind_self(signature, report_type) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: From a9b0b68af266c5c97e59215bfd62014bbfb8ed76 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 15:30:44 +0300 Subject: [PATCH 22/47] send report_type to classmethod --- mypy/checkmember.py | 18 +++++++++++------- mypy/types.py | 10 +++++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 01177844057a..3193163e0aff 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -123,7 +123,8 @@ def analyze_member_access(name: str, # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. result = analyze_class_attribute_access(ret_type, name, node, is_lvalue, - builtin_type, not_ready_callback, msg) + builtin_type, not_ready_callback, msg, + report_type=report_type) if result: return result # Look up from the 'type' type. @@ -155,7 +156,8 @@ def analyze_member_access(name: str, if item and not is_operator: # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, - builtin_type, not_ready_callback, msg) + builtin_type, not_ready_callback, msg, + report_type=report_type) if result: return result fallback = builtin_type('builtins.type') @@ -321,7 +323,8 @@ def analyze_class_attribute_access(itype: Instance, is_lvalue: bool, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], - msg: MessageBuilder) -> Type: + msg: MessageBuilder, + report_type: Type = None) -> Type: node = itype.type.get(name) if not node: if itype.type.fallback_to_any: @@ -344,7 +347,7 @@ def analyze_class_attribute_access(itype: Instance, if isinstance(t, PartialType): return handle_partial_attribute_type(t, is_lvalue, msg, node.node) is_classmethod = is_decorated and cast(Decorator, node.node).func.is_class - return add_class_tvars(t, itype, is_classmethod, builtin_type) + return add_class_tvars(t, itype, is_classmethod, builtin_type, report_type) elif isinstance(node.node, Var): not_ready_callback(name, context) return AnyType() @@ -364,18 +367,19 @@ def analyze_class_attribute_access(itype: Instance, def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, - builtin_type: Callable[[str], Instance]) -> Type: + builtin_type: Callable[[str], Instance], + report_type: Type = None) -> Type: info = itype.type # type: TypeInfo if isinstance(t, CallableType): # TODO: Should we propagate type variable values? vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, itype) + t = bind_self(t, report_type.item if isinstance(report_type, TypeType) else itype) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, - builtin_type)) + builtin_type, report_type)) for i in t.items()]) return t diff --git a/mypy/types.py b/mypy/types.py index 66490dda44dc..f99cb7e4695b 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1552,11 +1552,11 @@ def bind_self(t: F, actual_self: Type = None) -> F: instantiator = TypeVarInstantiator(self_arg.id, actual_self) arg_types = [t.accept(instantiator) for t in arg_types] ret_type = ret_type.accept(instantiator) - #FIX: why is this partial? - #from mypy.expandtype import expand_type - #arg_types = [expand_type(t, {self_arg.id: actual_self}) - # for t in arg_types] - #ret_type = expand_type(ret_type, {self_arg.id: actual_self}) + # FIX: why is this partial? + # from mypy.expandtype import expand_type + # arg_types = [expand_type(t, {self_arg.id: actual_self}) + # for t in arg_types] + # ret_type = expand_type(ret_type, {self_arg.id: actual_self}) variables = variables[1:] res = t.copy_modified(arg_types=arg_types, From ec0f33ad2298c391b020160ab833a1f9ecbeaeac Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 15:38:40 +0300 Subject: [PATCH 23/47] add missing file --- test-data/unit/check-selftype.test | 202 +++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 test-data/unit/check-selftype.test diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test new file mode 100644 index 000000000000..494b6531bbf1 --- /dev/null +++ b/test-data/unit/check-selftype.test @@ -0,0 +1,202 @@ +[case testSelfTypeInstance] +from typing import TypeVar + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + pass + +reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' +reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' +reveal_type(A().copy()) # E: Revealed type is '__main__.A' +reveal_type(B().copy()) # E: Revealed type is '__main__.B' +# FIX: Why partial type? +#reveal_type(A.copy(A())) E: Revealed type is '__main__.A' +#reveal_type(B.copy(B())) E: Revealed type is '__main__.B' + + +[case testSelfTypeReturn] +# flags: --hide-error-context +from typing import TypeVar, Type + +T = TypeVar('T') + +def _type(self: T) -> Type[T]: ... + +class A: + def copy(self: T) -> T: + if B(): + return A() # E: Incompatible return value type (got "A", expected "T") + elif A(): + return B() # E: Incompatible return value type (got "B", expected "T") + return _type(self)() + +class B(A): + pass + +class C: + def __init__(self, a: int) -> None: pass + + def copy(self: T) -> T: + if self: + return _type(self)(1) + else: + return _type(self)() # E: Too few arguments for "C" + + + +[case testSelfTypeClass] +# flags: --hide-error-context +from typing import TypeVar, Type + +T = TypeVar('T') + +class A: + @classmethod + def new(cls: Type[T]) -> T: + return cls() + +class B(A): + pass + +class C: + def __init__(self, a: int) -> None: pass + + @classmethod + def new(cls: Type[T]) -> T: + if cls: + return cls(1) + else: + return cls() # E: Too few arguments for "C" + + +reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A' +reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B' +reveal_type(A.new()) # E: Revealed type is '__main__.A' +reveal_type(B.new()) # E: Revealed type is '__main__.B' + +[builtins fixtures/classmethod.pyi] + +[case testSelfTypeOverride] +from typing import TypeVar, cast + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + pass + +class C(A): + def copy(self: T) -> T: ... + +reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' +reveal_type(C().copy()) # E: Revealed type is '__main__.C' +reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' +reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' + +[case testSelfTypeSuper] +# flags: --hide-error-context +from typing import TypeVar, cast + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: ... + +class B(A): + def copy(self: T) -> T: + reveal_type(self) # E: Revealed type is 'T`-1' + ## TODO: + ## super() dispatch is A.copy, but the type binding should be T=B + # reveal_type(super().copy()) # Revealed type is 'T`-1' + # return super().copy() + return cast(T, super().copy()) + +[case testSelfTypeRecursiveBinding] +# flags: --hide-error-context +from typing import TypeVar, Callable, Type + +T = TypeVar('T') +class A: + def copy(self: T, factory: Callable[[T], T]) -> T: + return factory(self) + + @classmethod + def new(cls: Type[T], factory: Callable[[T], T]) -> T: + reveal_type(cls) # E: Revealed type is 'Type[T`-1]' + reveal_type(cls()) # E: Revealed type is 'T`-1' + cls(2) # E: Too many arguments for "A" + return cls() + +class B(A): + pass + +reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' +reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' + +[builtins fixtures/classmethod.pyi] + +[case testSelfTypeBound] +# flags: --hide-error-context +from typing import TypeVar, Callable, cast + +T = TypeVar('T') + +class A: + def copy(self: T) -> T: + ... + +class C(A): + def copy(self: C) -> C: # E: Return type of "copy" incompatible with supertype "A" + ... + +class D(A): + def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" + ... + +class E(A): + def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" + ... + +class B(A): + x = 1 + def copy(self: T) -> T: + reveal_type(self.x) # E: Revealed type is 'builtins.int' + return cast(T, None) + +-- # TODO: fail for this +-- [case testSelfTypeBare] +-- class E: +-- def copy(self: T, other: T) -> T: ... + +[case testSelfTypeClone] +# flags: --hide-error-context +from typing import TypeVar, Type + +T = TypeVar('T', bound='C') + +class C: + def copy(self: T) -> T: + return self + + @classmethod + def new(cls: Type[T]) -> T: + return cls() + +def clone(arg: T) -> T: + reveal_type(arg.copy) # E: Revealed type is 'def () -> T`-1' + return arg.copy() + + +def make(cls: Type[T]) -> T: + reveal_type(cls.new) # E: Revealed type is 'def () -> T`-1' + return cls.new() + +[builtins fixtures/classmethod.pyi] \ No newline at end of file From 09cd7bc403c720611d58e30cca95585e3eda9bee Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 7 Oct 2016 16:21:27 +0300 Subject: [PATCH 24/47] more report type --- mypy/checkmember.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3193163e0aff..ef747cb2fb10 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -69,7 +69,7 @@ def analyze_member_access(name: str, # the first argument. pass else: - signature = bind_self(signature, report_type) + signature = bind_self(signature, report_type or typ) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: @@ -198,7 +198,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: function = function_type(method, builtin_type('builtins.function')) - bound_method = bind_self(function, itype) + bound_method = bind_self(function, report_type or itype) typ = map_instance_to_supertype(itype, method.info) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): @@ -219,7 +219,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, is_lvalue: bool, msg: MessageBuilder, - not_ready_callback: Callable[[str, Context], None]) -> Type: + not_ready_callback: Callable[[str, Context], None], + report_type: Type = None) -> Type: """Analyze access to an attribute via a Var node. This is conceptually part of analyze_member_access and the arguments are similar. @@ -248,7 +249,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = bind_self(functype) + signature = bind_self(functype, report_type or itype) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. From 8b242e25e65af2ec7938d97e0e81b3ec65e3e614 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 9 Oct 2016 01:57:14 +0300 Subject: [PATCH 25/47] support super() --- mypy/checker.py | 4 ++++ mypy/checkexpr.py | 11 +++++++---- test-data/unit/check-selftype.test | 7 ++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d3288c2ece27..00cdefac4910 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -603,6 +603,10 @@ def is_implicit_any(t: Type) -> bool: # Type check body in a new scope. with self.binder.frame_context(): + # To be used for super in visit_super in checkexpr + # (We don't care when it's not a method) + if item.arguments: + self.binder.push(NameExpr('__SelfType'), item.arguments[0].variable.type) self.accept(item.body) self.return_types.pop() diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 3d322c46ce9d..4f966913254f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1609,10 +1609,13 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.in_checked_function(): return AnyType() - return analyze_member_access(e.name, fill_typevars(e.info), e, - is_lvalue, True, False, - self.named_type, self.not_ready_callback, - self.msg, base, chk=self.chk) + selftype = self.chk.binder.get(NameExpr('__SelfType')) + return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e, + is_lvalue=False, is_super=True, is_operator=False, + builtin_type=self.named_type, + not_ready_callback=self.not_ready_callback, + msg=self.msg, override_info=base, chk=self.chk, + report_type=selftype) else: # Invalid super. This has been reported by the semantic analyzer. return AnyType() diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 494b6531bbf1..fc983678e335 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -111,11 +111,8 @@ class A: class B(A): def copy(self: T) -> T: reveal_type(self) # E: Revealed type is 'T`-1' - ## TODO: - ## super() dispatch is A.copy, but the type binding should be T=B - # reveal_type(super().copy()) # Revealed type is 'T`-1' - # return super().copy() - return cast(T, super().copy()) + reveal_type(super().copy) # E: Revealed type is 'def () -> T`-1' + return super().copy() [case testSelfTypeRecursiveBinding] # flags: --hide-error-context From a8be2a675f5002cb053e05343a09bcc7e52813c6 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 10 Oct 2016 00:36:37 +0300 Subject: [PATCH 26/47] avoid partial types when testing static access --- test-data/unit/check-selftype.test | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index fc983678e335..1f46cd6970e9 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -13,10 +13,30 @@ reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' reveal_type(A().copy()) # E: Revealed type is '__main__.A' reveal_type(B().copy()) # E: Revealed type is '__main__.B' -# FIX: Why partial type? -#reveal_type(A.copy(A())) E: Revealed type is '__main__.A' -#reveal_type(B.copy(B())) E: Revealed type is '__main__.B' +[case testSelfTypeStaticAccess] +from typing import TypeVar + +T = TypeVar('T') +class A: + def copy(self: T) -> T: ... + +class B(A): + pass + +# Partial types appear on reveal_type; unrelated to self type +def f(a: A) -> None: ... +f(A.copy(A())) +f(A.copy(B())) +f(B.copy(B())) + +# TODO: make it an error +# f(B.copy(A())) + +def g(a: B) -> None: ... +g(A.copy(A())) # E: Argument 1 to "g" has incompatible type "A"; expected "B" +g(A.copy(B())) +g(B.copy(B())) [case testSelfTypeReturn] # flags: --hide-error-context From 3055ab0ea19a64b98c01f6775c83e4dd78cba9cd Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 10 Oct 2016 00:43:08 +0300 Subject: [PATCH 27/47] remove comment --- mypy/checker.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 00cdefac4910..f691cd107be9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -582,9 +582,6 @@ def is_implicit_any(t: Type) -> bool: if arg_type.variance == COVARIANT: self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, arg_type) - # TODO: - # if arg_type.variance == SELF_VARIANCE and i > 0: - # self.fail("Cannot use bare self type in method arguments", arg_type) if typ.arg_kinds[i] == nodes.ARG_STAR: # builtins.tuple[T] is typing.Tuple[T, ...] arg_type = self.named_generic_type('builtins.tuple', From bb2ac78ec2605afeba75087ec72f432d072cd6ea Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 14 Oct 2016 06:32:38 +0300 Subject: [PATCH 28/47] use existing machinery; still partial types --- mypy/checker.py | 12 ++-- mypy/checkmember.py | 2 +- mypy/subtypes.py | 7 +- mypy/typeanal.py | 3 +- mypy/types.py | 67 ++++++++++--------- test-data/unit/check-selftype.test | 88 +++++++++++++++---------- test-data/unit/fixtures/classmethod.pyi | 1 + typeshed | 2 +- 8 files changed, 99 insertions(+), 83 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a3d2b5085406..2ce454b9f6ab 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -38,7 +38,7 @@ from mypy.checkmember import map_type_from_supertype from mypy import messages from mypy.subtypes import ( - is_subtype, is_equivalent, is_proper_subtype, + is_subtype, is_equivalent, is_proper_subtype, is_callable_subtype, is_more_precise, restrict_subtype_away ) from mypy.maptype import map_instance_to_supertype @@ -581,10 +581,12 @@ def is_implicit_any(t: Type) -> bool: arg_type = typ.arg_types[i] # Refuse covariant parameter type variables - if isinstance(arg_type, TypeVarType): - if arg_type.variance == COVARIANT: - self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, - arg_type) + if isinstance(arg_type, TypeVarType): + if i > 0: + if arg_type.variance == COVARIANT: + self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, + arg_type) + #FIX: if i == 0 and this is not a method then same as above if typ.arg_kinds[i] == nodes.ARG_STAR: # builtins.tuple[T] is typing.Tuple[T, ...] arg_type = self.named_generic_type('builtins.tuple', diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ef747cb2fb10..82fa4ea122e9 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -376,7 +376,7 @@ def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, report_type.item if isinstance(report_type, TypeType) else itype) + t = bind_self(t, report_type if isinstance(report_type, TypeType) else TypeType(itype)) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, diff --git a/mypy/subtypes.py b/mypy/subtypes.py index fff5df0f11e5..6fc335d98735 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -140,10 +140,9 @@ def visit_instance(self, left: Instance) -> bool: def visit_type_var(self, left: TypeVarType) -> bool: right = self.right - if isinstance(right, TypeVarType): - return left.id == right.id - else: - return is_subtype(left.upper_bound, self.right) + if isinstance(right, TypeVarType) and left.id == right.id: + return True + return is_subtype(left.upper_bound, self.right) def visit_callable_type(self, left: CallableType) -> bool: right = self.right diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7cb0e63d4edf..7fae80e829b2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -101,8 +101,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: ret = TypeVarType(sym.tvar_def, t.line) if t.enclosing_type: # SelfType - ret.variance = nodes.INVARIANT - ret.upper_bound = t.enclosing_type + pass return ret elif fullname == 'builtins.None': if experiments.STRICT_OPTIONAL: diff --git a/mypy/types.py b/mypy/types.py index f99cb7e4695b..6f2d2113ac5a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1532,47 +1532,46 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike F = TypeVar('F', bound=FunctionLike) -def bind_self(t: F, actual_self: Type = None) -> F: - if isinstance(t, Overloaded): - return cast(F, Overloaded([bind_self(c, t) for c in t.items()])) - assert isinstance(t, CallableType) +def bind_self(f: F, actual_self: Type = None) -> F: + from mypy.infer import infer_type_arguments + from mypy.applytype import apply_generic_arguments + from mypy.messages import MessageBuilder + + if isinstance(f, Overloaded): + return cast(F, Overloaded([bind_self(c, f) for c in f.items()])) + assert isinstance(f, CallableType) + t = f if t.arg_kinds and t.arg_kinds[0] == mypy.nodes.ARG_STAR: # The signature is of the form 'def foo(*args, ...)'. # In this case we shouldn't drop the first arg, # since t will be absorbed by the *args. return cast(F, t) - ret_type = t.ret_type - variables = list(t.variables) - arg_types = t.arg_types[1:] - if t.arg_types and actual_self is not None: - self_arg = t.arg_types[0] - if isinstance(self_arg, TypeType): - self_arg = self_arg.item - if isinstance(self_arg, TypeVarType): - instantiator = TypeVarInstantiator(self_arg.id, actual_self) - arg_types = [t.accept(instantiator) for t in arg_types] - ret_type = ret_type.accept(instantiator) - # FIX: why is this partial? - # from mypy.expandtype import expand_type - # arg_types = [expand_type(t, {self_arg.id: actual_self}) - # for t in arg_types] - # ret_type = expand_type(ret_type, {self_arg.id: actual_self}) - variables = variables[1:] - - res = t.copy_modified(arg_types=arg_types, + assert t.arg_types + self_param_type = t.arg_types[0] + + if (isinstance(self_param_type, TypeVarType) + or isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType)): + + if actual_self is None: + #TODO: type value restriction as union? + actual_self = erase_to_bound(self_param_type) + + typearg = infer_type_arguments([x.id for x in t.variables], t.arg_types[0], actual_self)[0] + t = cast(CallableType, apply_generic_arguments(t, [typearg] + [None for _ in t.variables[1:]], + None, t)) + + res = t.copy_modified(arg_types=t.arg_types[1:], arg_kinds=t.arg_kinds[1:], arg_names=t.arg_names[1:], - variables=variables, - ret_type=ret_type) + variables=t.variables, + ret_type=t.ret_type) return cast(F, res) -class TypeVarInstantiator(TypeTranslator): - def __init__(self, formal: TypeVarId, actual: Type) -> None: - self.formal = formal - self.actual = actual - - def visit_type_var(self, t: TypeVarType) -> Type: - if t.id == self.formal: - return self.actual - return t +def erase_to_bound(t: Type): + if isinstance(t, TypeVarType): + return t.upper_bound + if isinstance(t, TypeType): + if isinstance(t.item, TypeVarType): + return TypeType(t.item.upper_bound) + assert not t diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 1f46cd6970e9..73294f3d7c64 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1,10 +1,10 @@ [case testSelfTypeInstance] from typing import TypeVar -T = TypeVar('T') +T = TypeVar('T', bound='A', covariant=True) class A: - def copy(self: T) -> T: ... + def copy(self: T) -> T: pass class B(A): pass @@ -14,18 +14,20 @@ reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' reveal_type(A().copy()) # E: Revealed type is '__main__.A' reveal_type(B().copy()) # E: Revealed type is '__main__.B' +[builtins fixtures/bool.pyi] + [case testSelfTypeStaticAccess] from typing import TypeVar -T = TypeVar('T') +T = TypeVar('T', bound='A', covariant=True) class A: - def copy(self: T) -> T: ... + def copy(self: T) -> T: pass class B(A): pass # Partial types appear on reveal_type; unrelated to self type -def f(a: A) -> None: ... +def f(a: A) -> None: pass f(A.copy(A())) f(A.copy(B())) f(B.copy(B())) @@ -33,19 +35,21 @@ f(B.copy(B())) # TODO: make it an error # f(B.copy(A())) -def g(a: B) -> None: ... +def g(a: B) -> None: pass g(A.copy(A())) # E: Argument 1 to "g" has incompatible type "A"; expected "B" g(A.copy(B())) g(B.copy(B())) +[builtins fixtures/bool.pyi] + [case testSelfTypeReturn] # flags: --hide-error-context from typing import TypeVar, Type -T = TypeVar('T') - -def _type(self: T) -> Type[T]: ... +R = TypeVar('R') +def _type(self: R) -> Type[R]: pass +T = TypeVar('T', bound='A', covariant=True) class A: def copy(self: T) -> T: if B(): @@ -57,22 +61,24 @@ class A: class B(A): pass +Q = TypeVar('Q', bound='C', covariant=True) class C: def __init__(self, a: int) -> None: pass - def copy(self: T) -> T: + def copy(self: Q) -> Q: if self: return _type(self)(1) else: return _type(self)() # E: Too few arguments for "C" +[builtins fixtures/bool.pyi] [case testSelfTypeClass] # flags: --hide-error-context from typing import TypeVar, Type -T = TypeVar('T') +T = TypeVar('T', bound='A') class A: @classmethod @@ -82,11 +88,12 @@ class A: class B(A): pass +Q = TypeVar('Q', bound='C', covariant=True) class C: def __init__(self, a: int) -> None: pass @classmethod - def new(cls: Type[T]) -> T: + def new(cls: Type[Q]) -> Q: if cls: return cls(1) else: @@ -103,42 +110,48 @@ reveal_type(B.new()) # E: Revealed type is '__main__.B' [case testSelfTypeOverride] from typing import TypeVar, cast -T = TypeVar('T') +T = TypeVar('T', bound='A', covariant=True) class A: - def copy(self: T) -> T: ... + def copy(self: T) -> T: pass class B(A): pass +Q = TypeVar('Q', bound='C', covariant=True) class C(A): - def copy(self: T) -> T: ... + def copy(self: Q) -> Q: pass reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' reveal_type(C().copy()) # E: Revealed type is '__main__.C' reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' +[builtins fixtures/bool.pyi] + [case testSelfTypeSuper] # flags: --hide-error-context from typing import TypeVar, cast -T = TypeVar('T') +T = TypeVar('T', bound='A', covariant=True) class A: - def copy(self: T) -> T: ... + def copy(self: T) -> T: pass +Q = TypeVar('Q', bound='B', covariant=True) class B(A): - def copy(self: T) -> T: - reveal_type(self) # E: Revealed type is 'T`-1' - reveal_type(super().copy) # E: Revealed type is 'def () -> T`-1' + def copy(self: Q) -> Q: + reveal_type(self) # E: Revealed type is 'Q`-1' + reveal_type(super().copy) # E: Revealed type is 'def () -> Q`-1' return super().copy() +[builtins fixtures/bool.pyi] + [case testSelfTypeRecursiveBinding] # flags: --hide-error-context from typing import TypeVar, Callable, Type -T = TypeVar('T') +T = TypeVar('T', bound='A', covariant=True) class A: def copy(self: T, factory: Callable[[T], T]) -> T: return factory(self) @@ -153,10 +166,10 @@ class A: class B(A): pass -reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' -reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' +reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' +reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' +reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' [builtins fixtures/classmethod.pyi] @@ -164,34 +177,37 @@ reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __ma # flags: --hide-error-context from typing import TypeVar, Callable, cast -T = TypeVar('T') +TA = TypeVar('TA', bound='A', covariant=True) class A: - def copy(self: T) -> T: - ... + def copy(self: TA) -> TA: + pass class C(A): def copy(self: C) -> C: # E: Return type of "copy" incompatible with supertype "A" - ... + pass -class D(A): +class D(A): def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" - ... + pass class E(A): def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" - ... + pass +TB = TypeVar('TB', bound='B', covariant=True) class B(A): x = 1 - def copy(self: T) -> T: + def copy(self: TB) -> TB: reveal_type(self.x) # E: Revealed type is 'builtins.int' - return cast(T, None) + return cast(TB, None) + +[builtins fixtures/bool.pyi] -- # TODO: fail for this -- [case testSelfTypeBare] -- class E: --- def copy(self: T, other: T) -> T: ... +-- def copy(self: T, other: T) -> T: pass [case testSelfTypeClone] # flags: --hide-error-context @@ -216,4 +232,4 @@ def make(cls: Type[T]) -> T: reveal_type(cls.new) # E: Revealed type is 'def () -> T`-1' return cls.new() -[builtins fixtures/classmethod.pyi] \ No newline at end of file +[builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/fixtures/classmethod.pyi b/test-data/unit/fixtures/classmethod.pyi index f6333bd7b289..282839dcef28 100644 --- a/test-data/unit/fixtures/classmethod.pyi +++ b/test-data/unit/fixtures/classmethod.pyi @@ -19,3 +19,4 @@ class int: class str: pass class bytes: pass +class bool: pass diff --git a/typeshed b/typeshed index f90a6d1c0eed..aa549db5e5e5 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit f90a6d1c0eeda7b2b267d9bbfcc6681f4d9f8168 +Subproject commit aa549db5e5e57ee2702899d1cc660163b52171ed From 5f26cacfcd9966f529f13561634b8a652685a19c Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 14 Oct 2016 13:42:24 +0300 Subject: [PATCH 29/47] do not trigger the .erased flag --- mypy/expandtype.py | 12 +++++++----- mypy/types.py | 26 +++++++++++++++++--------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index c299163b747b..c4871a23839e 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -1,4 +1,4 @@ -from typing import Dict, Tuple, List, cast +from typing import Dict, List, cast from mypy.types import ( Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType, @@ -7,12 +7,12 @@ ) -def expand_type(typ: Type, env: Dict[TypeVarId, Type]) -> Type: +def expand_type(typ: Type, env: Dict[TypeVarId, Type], erase_instances: bool = True) -> Type: """Substitute any type variable references in a type given by a type environment. """ - return typ.accept(ExpandTypeVisitor(env)) + return typ.accept(ExpandTypeVisitor(env, erase_instances=erase_instances)) def expand_type_by_instance(typ: Type, instance: Instance) -> Type: @@ -32,9 +32,11 @@ class ExpandTypeVisitor(TypeVisitor[Type]): """Visitor that substitutes type variables with values.""" variables = None # type: Dict[TypeVarId, Type] # TypeVar id -> TypeVar value + erase_instances = True # type: bool - def __init__(self, variables: Dict[TypeVarId, Type]) -> None: + def __init__(self, variables: Dict[TypeVarId, Type], erase_instances=True) -> None: self.variables = variables + self.erase_instances = erase_instances def visit_unbound_type(self, t: UnboundType) -> Type: return t @@ -74,7 +76,7 @@ def visit_type_var(self, t: TypeVarType) -> Type: inst = repl # Return copy of instance with type erasure flag on. return Instance(inst.type, inst.args, line=inst.line, - column=inst.column, erased=True) + column=inst.column, erased=self.erase_instances) else: return repl diff --git a/mypy/types.py b/mypy/types.py index 6f2d2113ac5a..091a698e10a8 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -418,7 +418,7 @@ class Instance(Type): type = None # type: mypy.nodes.TypeInfo args = None # type: List[Type] - erased = False # True if result of type variable substitution + erased = False # True if result of type variable substitution def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type], line: int = -1, column: int = -1, erased: bool = False) -> None: @@ -1536,6 +1536,7 @@ def bind_self(f: F, actual_self: Type = None) -> F: from mypy.infer import infer_type_arguments from mypy.applytype import apply_generic_arguments from mypy.messages import MessageBuilder + from mypy.expandtype import expand_type if isinstance(f, Overloaded): return cast(F, Overloaded([bind_self(c, f) for c in f.items()])) @@ -1545,26 +1546,33 @@ def bind_self(f: F, actual_self: Type = None) -> F: # The signature is of the form 'def foo(*args, ...)'. # In this case we shouldn't drop the first arg, # since t will be absorbed by the *args. + + # TODO: infer bounds on the type of *args? return cast(F, t) assert t.arg_types self_param_type = t.arg_types[0] - + arg_types = t.arg_types[1:] + ret_type = t.ret_type + variables = t.variables if (isinstance(self_param_type, TypeVarType) or isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType)): if actual_self is None: - #TODO: type value restriction as union? + #TODO: value restriction as union? actual_self = erase_to_bound(self_param_type) typearg = infer_type_arguments([x.id for x in t.variables], t.arg_types[0], actual_self)[0] - t = cast(CallableType, apply_generic_arguments(t, [typearg] + [None for _ in t.variables[1:]], - None, t)) - - res = t.copy_modified(arg_types=t.arg_types[1:], + def expand(target: Type) -> Type: + return expand_type(target, {t.variables[0].id : typearg}, False) + arg_types = [expand(x) for x in arg_types] + ret_type = expand(t.ret_type) + variables = variables[1:] + + res = t.copy_modified(arg_types=arg_types, arg_kinds=t.arg_kinds[1:], arg_names=t.arg_names[1:], - variables=t.variables, - ret_type=t.ret_type) + variables=variables, + ret_type=ret_type) return cast(F, res) From fcbcf4b25d8e3901c0e8cd16b42797374aebf436 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 14 Oct 2016 17:44:46 +0300 Subject: [PATCH 30/47] Fix issue with t.variables. adapt SelfTypeBound --- mypy/checker.py | 4 ++-- mypy/checkmember.py | 16 ++++++++++------ test-data/unit/check-selftype.test | 24 +++++++++++++++--------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 466bb2e38872..e9acab932a75 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -580,12 +580,12 @@ def is_implicit_any(t: Type) -> bool: arg_type = typ.arg_types[i] # Refuse covariant parameter type variables - if isinstance(arg_type, TypeVarType): + if isinstance(arg_type, TypeVarType): if i > 0: if arg_type.variance == COVARIANT: self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, arg_type) - #FIX: if i == 0 and this is not a method then same as above + # FIX: if i == 0 and this is not a method then same as above if typ.arg_kinds[i] == nodes.ARG_STAR: # builtins.tuple[T] is typing.Tuple[T, ...] arg_type = self.named_generic_type('builtins.tuple', diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ded4a7971533..58298db64920 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -515,19 +515,23 @@ def bind_self(f: F, actual_self: Type = None) -> F: # The signature is of the form 'def foo(*args, ...)'. # In this case we shouldn't drop the first arg, # since t will be absorbed by the *args. - + # TODO: infer bounds on the type of *args? return cast(F, t) self_param_type = t.arg_types[0] - if (isinstance(self_param_type, TypeVarType) - or isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType)): + if t.variables and (isinstance(self_param_type, TypeVarType) or + (isinstance(self_param_type, TypeType) and + isinstance(self_param_type.item, TypeVarType))): if actual_self is None: - #TODO: value restriction as union? + # XXX value restriction as union? actual_self = erase_to_bound(self_param_type) - typearg = infer_type_arguments([x.id for x in t.variables], t.arg_types[0], actual_self)[0] + typearg = infer_type_arguments([x.id for x in t.variables], + self_param_type, actual_self)[0] + def expand(target: Type) -> Type: - return expand_type(target, {t.variables[0].id : typearg}, False) + return expand_type(target, {t.variables[0].id: typearg}, False) + arg_types = [expand(x) for x in t.arg_types[1:]] ret_type = expand(t.ret_type) variables = t.variables[1:] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 73294f3d7c64..e16a9cafd008 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -26,7 +26,7 @@ class A: class B(A): pass -# Partial types appear on reveal_type; unrelated to self type +# Erased instances appear on reveal_type; unrelated to self type def f(a: A) -> None: pass f(A.copy(A())) f(A.copy(B())) @@ -184,16 +184,17 @@ class A: pass class C(A): - def copy(self: C) -> C: # E: Return type of "copy" incompatible with supertype "A" + def copy(self: C) -> C: pass -class D(A): - def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" - pass - -class E(A): - def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" - pass +-- # TODO: +-- class D(A): +-- def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" +-- pass +-- +-- class E(A): +-- def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" +-- pass TB = TypeVar('TB', bound='B', covariant=True) class B(A): @@ -206,6 +207,11 @@ class B(A): -- # TODO: fail for this -- [case testSelfTypeBare] +-- # flags: --hide-error-context +-- from typing import TypeVar, Type +-- +-- T = TypeVar('T', bound='E') +-- -- class E: -- def copy(self: T, other: T) -> T: pass From 860c96ac4b55497c1c52c8944490f9277b7d5185 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 16 Oct 2016 00:09:54 +0300 Subject: [PATCH 31/47] minor --- mypy/checkmember.py | 50 ++++++++++++++++-------------- mypy/semanal.py | 2 -- mypy/typeanal.py | 10 +----- mypy/types.py | 3 -- test-data/unit/check-selftype.test | 2 +- 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 58298db64920..bc55df3c319d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -70,7 +70,7 @@ def analyze_member_access(name: str, # the first argument. pass else: - signature = bind_self(signature, report_type or typ) + signature = bind_self(signature, report_type) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: @@ -183,6 +183,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, This is logically part of analyze_member_access and the arguments are similar. """ + report_type = report_type or itype # It was not a method. Try looking up a variable. v = lookup_member_var_or_accessor(info, name, is_lvalue) @@ -199,7 +200,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: function = function_type(method, builtin_type('builtins.function')) - bound_method = bind_self(function, report_type or itype) + bound_method = bind_self(function, report_type) typ = map_instance_to_supertype(itype, method.info) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): @@ -215,7 +216,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, else: if chk and chk.should_suppress_optional_error([itype]): return AnyType() - return msg.has_no_attr(report_type or itype, name, node) + return msg.has_no_attr(report_type, name, node) def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, @@ -226,6 +227,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont This is conceptually part of analyze_member_access and the arguments are similar. """ + report_type = report_type or itype # Found a member variable. itype = map_instance_to_supertype(itype, var.info) typ = var.type @@ -250,7 +252,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = bind_self(functype, report_type or itype) + signature = bind_self(functype, report_type) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -507,41 +509,41 @@ def bind_self(f: F, actual_self: Type = None) -> F: if isinstance(f, Overloaded): return cast(F, Overloaded([bind_self(c, f) for c in f.items()])) assert isinstance(f, CallableType) - t = f - if not t.arg_types: + func = f + if not func.arg_types: # invalid method. return something - return cast(F, t) - if t.arg_kinds[0] == ARG_STAR: + return cast(F, func) + if func.arg_kinds[0] == ARG_STAR: # The signature is of the form 'def foo(*args, ...)'. - # In this case we shouldn't drop the first arg, - # since t will be absorbed by the *args. + # In this case we shouldn'func drop the first arg, + # since func will be absorbed by the *args. # TODO: infer bounds on the type of *args? - return cast(F, t) - self_param_type = t.arg_types[0] - if t.variables and (isinstance(self_param_type, TypeVarType) or + return cast(F, func) + self_param_type = func.arg_types[0] + if func.variables and (isinstance(self_param_type, TypeVarType) or (isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType))): if actual_self is None: # XXX value restriction as union? actual_self = erase_to_bound(self_param_type) - typearg = infer_type_arguments([x.id for x in t.variables], + typearg = infer_type_arguments([x.id for x in func.variables], self_param_type, actual_self)[0] def expand(target: Type) -> Type: - return expand_type(target, {t.variables[0].id: typearg}, False) + return expand_type(target, {func.variables[0].id: typearg}, False) - arg_types = [expand(x) for x in t.arg_types[1:]] - ret_type = expand(t.ret_type) - variables = t.variables[1:] + arg_types = [expand(x) for x in func.arg_types[1:]] + ret_type = expand(func.ret_type) + variables = func.variables[1:] else: - arg_types = t.arg_types[1:] - ret_type = t.ret_type - variables = t.variables - res = t.copy_modified(arg_types=arg_types, - arg_kinds=t.arg_kinds[1:], - arg_names=t.arg_names[1:], + arg_types = func.arg_types[1:] + ret_type = func.ret_type + variables = func.variables + res = func.copy_modified(arg_types=arg_types, + arg_kinds=func.arg_kinds[1:], + arg_names=func.arg_names[1:], variables=variables, ret_type=ret_type) return cast(F, res) diff --git a/mypy/semanal.py b/mypy/semanal.py index 7abc54624c3f..b05a48782f12 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -333,8 +333,6 @@ def prepare_method_signature(self, func: FuncDef) -> None: leading_type = fill_typevars(self.type) sig = cast(FunctionLike, func.type) func.type = replace_implicit_first_type(sig, leading_type) - elif isinstance(self_type, UnboundType): - self_type.enclosing_type = fill_typevars(self.type) def is_conditional_func(self, previous: Node, new: FuncDef) -> bool: """Does 'new' conditionally redefine 'previous'? diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7fae80e829b2..931cf7cf5363 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -98,11 +98,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Type variable "{}" used with arguments'.format( t.name), t) assert sym.tvar_def is not None - ret = TypeVarType(sym.tvar_def, t.line) - if t.enclosing_type: - # SelfType - pass - return ret + return TypeVarType(sym.tvar_def, t.line) elif fullname == 'builtins.None': if experiments.STRICT_OPTIONAL: return NoneTyp(is_ret_type=t.is_ret_type) @@ -143,10 +139,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Type[...] must have exactly one type argument', t) items = self.anal_array(t.args) item = items[0] - if t.enclosing_type and isinstance(item, TypeVarType): - # SelfType - item.variance = nodes.INVARIANT - item.upper_bound = t.enclosing_type return TypeType(item, line=t.line) elif sym.kind == TYPE_ALIAS: # TODO: Generic type aliases. diff --git a/mypy/types.py b/mypy/types.py index c28d3c8d317e..9c80b590cd38 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -182,9 +182,6 @@ class UnboundType(Type): # is this type a return type? is_ret_type = False - # If it's annotation for `self` parameter, the type of the enclosing class - enclosing_type = None # type: Type - # special case for X[()] empty_tuple_index = False diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index e16a9cafd008..7d7c0c33f8c4 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -183,7 +183,7 @@ class A: def copy(self: TA) -> TA: pass -class C(A): +class C(A): def copy(self: C) -> C: pass From b078d61d579b5447fa8164de5b119089a4e19f80 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 16 Oct 2016 02:28:35 +0300 Subject: [PATCH 32/47] some override checking: instantiate with fillvars(self) --- mypy/checker.py | 9 +++++++-- test-data/unit/check-selftype.test | 11 +++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e9acab932a75..c5fa3d9d3330 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -88,6 +88,9 @@ class TypeChecker(NodeVisitor[Type]): # Helper for type checking expressions expr_checker = None # type: mypy.checkexpr.ExpressionChecker + # Class context for selftyoe overriding + class_context = [] # type: List[Type] + # Stack of function return types return_types = None # type: List[Type] # Type context for type inference @@ -824,7 +827,7 @@ def check_method_override_for_base_with_name( # The name of the method is defined in the base class. # Construct the type of the overriding method. - typ = bind_self(self.function_type(defn)) + typ = bind_self(self.function_type(defn), self.class_context[-1]) # Map the overridden method type to subtype context so that # it can be checked for compatibility. original_type = base_attr.type @@ -837,7 +840,7 @@ def check_method_override_for_base_with_name( assert False, str(base_attr.node) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( - bind_self(original_type), + bind_self(original_type, self.class_context[-1]), defn.info, base) # Check that the types are compatible. # TODO overloaded signatures @@ -921,7 +924,9 @@ def visit_class_def(self, defn: ClassDef) -> Type: old_binder = self.binder self.binder = ConditionalTypeBinder() with self.binder.frame_context(): + self.class_context.append(fill_typevars(defn.info)) self.accept(defn.defs) + self.class_context.pop() self.binder = old_binder if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 7d7c0c33f8c4..d4b2fa8a0400 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -187,14 +187,9 @@ class C(A): def copy(self: C) -> C: pass --- # TODO: --- class D(A): --- def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" --- pass --- --- class E(A): --- def copy(self: A) -> E: # E: Return type of "copy" incompatible with supertype "A" --- pass +class D(A): + def copy(self: A) -> A: # E: Return type of "copy" incompatible with supertype "A" + pass TB = TypeVar('TB', bound='B', covariant=True) class B(A): From 2299a0075944cad0827a6398a37909323240c651 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 19 Oct 2016 01:40:31 +0300 Subject: [PATCH 33/47] forgotten file --- mypy/checker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 5c3168a48a9d..31129b044f53 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -610,6 +610,10 @@ def is_implicit_any(t: Type) -> bool: # Type check body in a new scope. with self.binder.top_frame_context(): + # To be used for super in visit_super in checkexpr + # (We don't care when it's not a method) + if item.arguments: + self.binder.push(NameExpr('__SelfType'), item.arguments[0].variable.type) self.accept(item.body) unreachable = self.binder.is_unreachable() @@ -950,6 +954,7 @@ def visit_class_def(self, defn: ClassDef) -> Type: old_binder = self.binder self.binder = ConditionalTypeBinder() with self.binder.top_frame_context(): + self.class_context.append(fill_typevars(defn.info)) self.accept(defn.defs) self.class_context.pop() self.binder = old_binder From 137f4524edd599502cf4c71e61e45759304f1151 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 19 Oct 2016 03:28:35 +0300 Subject: [PATCH 34/47] initialize mutable in __init__ --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 31129b044f53..feca21ded4ff 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -89,7 +89,7 @@ class TypeChecker(NodeVisitor[Type]): expr_checker = None # type: mypy.checkexpr.ExpressionChecker # Class context for selftyoe overriding - class_context = [] # type: List[Type] + class_context = None # type: List[Type] # Stack of function return types return_types = None # type: List[Type] @@ -133,6 +133,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: self.module_type_map = {} self.binder = ConditionalTypeBinder() self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg) + self.class_context = [] self.return_types = [] self.type_context = [] self.dynamic_funcs = [] From 4bcbf599f9dfc3ca32976ea3dd2801f535320317 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 20 Oct 2016 20:32:06 +0300 Subject: [PATCH 35/47] Rename report_type, Add docstrings etc. --- mypy/checker.py | 5 +- mypy/checkexpr.py | 2 +- mypy/checkmember.py | 106 ++++++++++++++++++++--------- test-data/unit/check-selftype.test | 7 +- 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index feca21ded4ff..7bcca729d8ed 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -88,7 +88,9 @@ class TypeChecker(NodeVisitor[Type]): # Helper for type checking expressions expr_checker = None # type: mypy.checkexpr.ExpressionChecker - # Class context for selftyoe overriding + # Class context for checking overriding of a method of the form + # def foo(self: T) -> T + # We need to pass the current class definition for instantiation of T class_context = None # type: List[Type] # Stack of function return types @@ -587,6 +589,7 @@ def is_implicit_any(t: Type) -> bool: arg_type = typ.arg_types[i] # Refuse covariant parameter type variables + # TODO: check recuresively for inner type variables if isinstance(arg_type, TypeVarType): if i > 0: if arg_type.variance == COVARIANT: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fd7a89e804bf..1e1a29197b20 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1611,7 +1611,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: builtin_type=self.named_type, not_ready_callback=self.not_ready_callback, msg=self.msg, override_info=base, chk=self.chk, - report_type=selftype) + actual_self=selftype) else: # Invalid super. This has been reported by the semantic analyzer. return AnyType() diff --git a/mypy/checkmember.py b/mypy/checkmember.py index bc55df3c319d..9e857b37a10c 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -31,7 +31,7 @@ def analyze_member_access(name: str, not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, override_info: TypeInfo = None, - report_type: Type = None, + actual_self: Type = None, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Analyse attribute access. @@ -40,8 +40,10 @@ def analyze_member_access(name: str, 1. lvalue or non-lvalue access (i.e. setter or getter access) 2. supertype access (when using super(); is_super == True and override_info should refer to the supertype) + + actual_self is the type of E in the expression E.foo """ - report_type = report_type or typ + actual_self = actual_self or typ if isinstance(typ, Instance): if name == '__init__' and not is_super: # Accessing __init__ in statically typed code would compromise @@ -70,7 +72,7 @@ def analyze_member_access(name: str, # the first argument. pass else: - signature = bind_self(signature, report_type) + signature = bind_self(signature, actual_self) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: @@ -78,7 +80,7 @@ def analyze_member_access(name: str, return analyze_member_var_access(name, typ, info, node, is_lvalue, is_super, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) elif isinstance(typ, AnyType): # The base object has dynamic type. return AnyType() @@ -88,7 +90,7 @@ def analyze_member_access(name: str, # The only attribute NoneType has are those it inherits from object return analyze_member_access(name, builtin_type('builtins.object'), node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) elif isinstance(typ, UnionType): # The base object has dynamic type. msg.disable_type_names += 1 @@ -125,24 +127,24 @@ def analyze_member_access(name: str, # See https://github.com/python/mypy/pull/1787 for more info. result = analyze_class_attribute_access(ret_type, name, node, is_lvalue, builtin_type, not_ready_callback, msg, - report_type=report_type) + actual_self=actual_self) if result: return result # Look up from the 'type' type. return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) else: assert False, 'Unexpected type {}'.format(repr(ret_type)) elif isinstance(typ, FunctionLike): # Look up from the 'function' type. return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) elif isinstance(typ, TypeVarType): return analyze_member_access(name, typ.upper_bound, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) elif isinstance(typ, DeletedType): msg.deleted_as_rvalue(typ, node) return AnyType() @@ -158,17 +160,17 @@ def analyze_member_access(name: str, # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, builtin_type, not_ready_callback, msg, - report_type=report_type) + actual_self=actual_self) if result: return result fallback = builtin_type('builtins.type') return analyze_member_access(name, fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - report_type=report_type, chk=chk) + actual_self=actual_self, chk=chk) if chk and chk.should_suppress_optional_error([typ]): return AnyType() - return msg.has_no_attr(report_type, name, node) + return msg.has_no_attr(actual_self, name, node) def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, @@ -176,14 +178,15 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, - report_type: Type = None, + actual_self: Type = None, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Analyse attribute access that does not target a method. - This is logically part of analyze_member_access and the arguments are - similar. + This is logically part of analyze_member_access and the arguments are similar. + + actual_self is the type of E in the expression E.var """ - report_type = report_type or itype + actual_self = actual_self or itype # It was not a method. Try looking up a variable. v = lookup_member_var_or_accessor(info, name, is_lvalue) @@ -200,7 +203,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: function = function_type(method, builtin_type('builtins.function')) - bound_method = bind_self(function, report_type) + bound_method = bind_self(function, actual_self) typ = map_instance_to_supertype(itype, method.info) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): @@ -216,18 +219,20 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, else: if chk and chk.should_suppress_optional_error([itype]): return AnyType() - return msg.has_no_attr(report_type, name, node) + return msg.has_no_attr(actual_self, name, node) def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, is_lvalue: bool, msg: MessageBuilder, not_ready_callback: Callable[[str, Context], None], - report_type: Type = None) -> Type: + actual_self: Type = None) -> Type: """Analyze access to an attribute via a Var node. This is conceptually part of analyze_member_access and the arguments are similar. + + actual_self is the type of E in the expression E.var """ - report_type = report_type or itype + actual_self = actual_self or itype # Found a member variable. itype = map_instance_to_supertype(itype, var.info) typ = var.type @@ -252,7 +257,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = bind_self(functype, report_type) + signature = bind_self(functype, actual_self) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -328,7 +333,8 @@ def analyze_class_attribute_access(itype: Instance, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, - report_type: Type = None) -> Type: + actual_self: Type = None) -> Type: + '''actual_self is the type of E in the expression E.var''' node = itype.type.get(name) if not node: if itype.type.fallback_to_any: @@ -351,7 +357,7 @@ def analyze_class_attribute_access(itype: Instance, if isinstance(t, PartialType): return handle_partial_attribute_type(t, is_lvalue, msg, node.node) is_classmethod = is_decorated and cast(Decorator, node.node).func.is_class - return add_class_tvars(t, itype, is_classmethod, builtin_type, report_type) + return add_class_tvars(t, itype, is_classmethod, builtin_type, actual_self) elif isinstance(node.node, Var): not_ready_callback(name, context) return AnyType() @@ -372,18 +378,32 @@ def analyze_class_attribute_access(itype: Instance, def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, builtin_type: Callable[[str], Instance], - report_type: Type = None) -> Type: + actual_self: Type = None) -> Type: + '''Instantiate type variables during analyze_class_attribute_access, + e.g T and Q in the following: + + def A(Generic(T)): + @classmethod + def foo(cls: Type[Q]) -> Tuple[T, Q]: ... + + class B(A): pass + + B.foo() + + actual_self is the value of the type B in the expression B.foo() + ''' + # TODO: verify consistency betweem Q and T info = itype.type # type: TypeInfo if isinstance(t, CallableType): # TODO: Should we propagate type variable values? vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, report_type if isinstance(report_type, TypeType) else TypeType(itype)) + t = bind_self(t, actual_self if isinstance(actual_self, TypeType) else TypeType(itype)) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, - builtin_type, report_type)) + builtin_type, actual_self)) for i in t.items()]) return t @@ -505,11 +525,34 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo, F = TypeVar('F', bound=FunctionLike) -def bind_self(f: F, actual_self: Type = None) -> F: - if isinstance(f, Overloaded): - return cast(F, Overloaded([bind_self(c, f) for c in f.items()])) - assert isinstance(f, CallableType) - func = f +def bind_self(method: F, actual_self: Type = None) -> F: + '''Return a copy of `method`, with the type of its first parameter (usually + self or cls) bound to actual_self. + + If the type of `self` is a generic type (T, or Type[T] for classmethods), + instantiate every occurrence of type with actual_self in the rest of the + signature and in the return type. + + actual_self is the type of E in the expression E.copy(). It is None in + compatibility checks. In this case we treat it as the erasure of the + declared type of self. + + This way we can express "the type of self". For example: + + T = TypeVar('T', covariant=True) + class A: + def copy(self: T) -> T: + ... + + class B(A): pass + + b = B().copy() # type: B + + ''' + if isinstance(method, Overloaded): + return cast(F, Overloaded([bind_self(c, method) for c in method.items()])) + assert isinstance(method, CallableType) + func = method if not func.arg_types: # invalid method. return something return cast(F, func) @@ -525,6 +568,7 @@ def bind_self(f: F, actual_self: Type = None) -> F: (isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType))): if actual_self is None: + # Type check method override # XXX value restriction as union? actual_self = erase_to_bound(self_param_type) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index d4b2fa8a0400..37b150c7ef29 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -56,7 +56,8 @@ class A: return A() # E: Incompatible return value type (got "A", expected "T") elif A(): return B() # E: Incompatible return value type (got "B", expected "T") - return _type(self)() + reveal_type(_type(self)) # E: Revealed type is 'Type[T`-1]' + return reveal_type(_type(self)()) # E: Revealed type is 'T`-1' class B(A): pass @@ -67,7 +68,7 @@ class C: def copy(self: Q) -> Q: if self: - return _type(self)(1) + return reveal_type(_type(self)(1)) # E: Revealed type is 'Q`-1' else: return _type(self)() # E: Too few arguments for "C" @@ -83,7 +84,7 @@ T = TypeVar('T', bound='A') class A: @classmethod def new(cls: Type[T]) -> T: - return cls() + return reveal_type(cls()) # E: Revealed type is 'T`-1' class B(A): pass From 6155c7f65efc70aaa17f9fa25fdfd07fa5e6953d Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 20 Oct 2016 21:17:34 +0300 Subject: [PATCH 36/47] minor doc fix --- mypy/checkmember.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 9e857b37a10c..88f87549b492 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -539,10 +539,9 @@ def bind_self(method: F, actual_self: Type = None) -> F: This way we can express "the type of self". For example: - T = TypeVar('T', covariant=True) + T = TypeVar('T', bound='A') class A: - def copy(self: T) -> T: - ... + def copy(self: T) -> T: ... class B(A): pass From 47e5e2fcf553f0618fdb763bac40568f6c049414 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 20 Oct 2016 21:31:57 +0300 Subject: [PATCH 37/47] remove binder hack --- mypy/checker.py | 4 ---- mypy/checkexpr.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7bcca729d8ed..b071045a5299 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -614,10 +614,6 @@ def is_implicit_any(t: Type) -> bool: # Type check body in a new scope. with self.binder.top_frame_context(): - # To be used for super in visit_super in checkexpr - # (We don't care when it's not a method) - if item.arguments: - self.binder.push(NameExpr('__SelfType'), item.arguments[0].variable.type) self.accept(item.body) unreachable = self.binder.is_unreachable() diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1e1a29197b20..473c03fbfbd4 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1605,7 +1605,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.in_checked_function(): return AnyType() - selftype = self.chk.binder.get(NameExpr('__SelfType')) + selftype = self.chk.function_stack[-1].arguments[0].variable.type return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e, is_lvalue=False, is_super=True, is_operator=False, builtin_type=self.named_type, From 30f847e974ee46ed7f104da336a7305a8ced7f09 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 18:02:28 +0300 Subject: [PATCH 38/47] example for actual_type --- mypy/checkmember.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0a504ee1c0b7..9e61fa0dc096 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -43,7 +43,16 @@ def analyze_member_access(name: str, 2. supertype access (when using super(); is_super == True and override_info should refer to the supertype) - actual_self is the type of E in the expression E.foo + actual_self is the type of E in the expression E.foo - the most precise + information available for mypy at the point of accessing E.foo + For example, + + class D(str): pass + a = D() + reveal_type(a.replace) + + during checking `a.replace`, typ will be str, whereas actual_self will be D. + This helps for error reporting and for implementing methods where self is generic. """ actual_self = actual_self or typ if isinstance(typ, Instance): From c152c2061449a8771b27867b3f9a3edb897c6071 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 20:26:01 +0300 Subject: [PATCH 39/47] avoid crush in super(); document --- mypy/checkexpr.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b8d9a9641a8b..221aea377569 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1608,13 +1608,17 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.in_checked_function(): return AnyType() - selftype = self.chk.function_stack[-1].arguments[0].variable.type - return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e, + # fill_typevars(e.info) erases type variables + erased_self = fill_typevars(e.info) + args = self.chk.function_stack[-1].arguments + # An empty args with super() is an error, but we need something in declared_self + declared_self = args[0].variable.type if args else erased_self + return analyze_member_access(name=e.name, typ=erased_self, node=e, is_lvalue=False, is_super=True, is_operator=False, builtin_type=self.named_type, not_ready_callback=self.not_ready_callback, msg=self.msg, override_info=base, chk=self.chk, - actual_self=selftype) + actual_self=declared_self) else: # Invalid super. This has been reported by the semantic analyzer. return AnyType() From 4f2017ee6e95c3185ea7ea4a7c2ca96fa05dc607 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 20:33:35 +0300 Subject: [PATCH 40/47] lint --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 221aea377569..00170dcf1871 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1611,7 +1611,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: # fill_typevars(e.info) erases type variables erased_self = fill_typevars(e.info) args = self.chk.function_stack[-1].arguments - # An empty args with super() is an error, but we need something in declared_self + # An empty args with super() is an error; we need something in declared_self declared_self = args[0].variable.type if args else erased_self return analyze_member_access(name=e.name, typ=erased_self, node=e, is_lvalue=False, is_super=True, is_operator=False, From fddbba03a36cab6423ff7fda105f70f66cf3a005 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 21:18:17 +0300 Subject: [PATCH 41/47] comment for fill_typevars --- mypy/checkexpr.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 00170dcf1871..e424aef26944 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1608,12 +1608,19 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.in_checked_function(): return AnyType() - # fill_typevars(e.info) erases type variables - erased_self = fill_typevars(e.info) + # If the class A is not generic, and we declare `self: T` in the signature: + # * declared_self will be T + # * fill_typevars(e.info) will be A + # If the class has a type arguments Q, and we declare `self: A[T]`: + # * declared_self will be A[T] + # * fill_typevars(e.info) will be A[Q] + # If the we declare `self: T` in a generic class, declared_self is still T. + # TODO: generic classes are not actually supported yet + filled_self = fill_typevars(e.info) args = self.chk.function_stack[-1].arguments # An empty args with super() is an error; we need something in declared_self - declared_self = args[0].variable.type if args else erased_self - return analyze_member_access(name=e.name, typ=erased_self, node=e, + declared_self = args[0].variable.type if args else filled_self + return analyze_member_access(name=e.name, typ=filled_self, node=e, is_lvalue=False, is_super=True, is_operator=False, builtin_type=self.named_type, not_ready_callback=self.not_ready_callback, From 3cf8ecf54b2685e2990fe617d5d04d977f9a79d6 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 21:19:07 +0300 Subject: [PATCH 42/47] comment for fill_typevars --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e424aef26944..98198683f76c 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1615,7 +1615,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: # * declared_self will be A[T] # * fill_typevars(e.info) will be A[Q] # If the we declare `self: T` in a generic class, declared_self is still T. - # TODO: generic classes are not actually supported yet + # TODO: selftype does not support generic classes yet filled_self = fill_typevars(e.info) args = self.chk.function_stack[-1].arguments # An empty args with super() is an error; we need something in declared_self From 707da4e4d46998263d2c456e38e3885a0aaa913a Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 21:44:13 +0300 Subject: [PATCH 43/47] rename actual_self, use declared_type in analyze_super --- mypy/checkexpr.py | 18 +++------ mypy/checkmember.py | 93 ++++++++++++++++++++------------------------- 2 files changed, 47 insertions(+), 64 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 98198683f76c..f2597b0679b1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1608,24 +1608,18 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: return AnyType() if not self.chk.in_checked_function(): return AnyType() - # If the class A is not generic, and we declare `self: T` in the signature: - # * declared_self will be T - # * fill_typevars(e.info) will be A - # If the class has a type arguments Q, and we declare `self: A[T]`: - # * declared_self will be A[T] - # * fill_typevars(e.info) will be A[Q] - # If the we declare `self: T` in a generic class, declared_self is still T. - # TODO: selftype does not support generic classes yet - filled_self = fill_typevars(e.info) args = self.chk.function_stack[-1].arguments # An empty args with super() is an error; we need something in declared_self - declared_self = args[0].variable.type if args else filled_self - return analyze_member_access(name=e.name, typ=filled_self, node=e, + if not args: + self.chk.fail('super() requires at least on positional argument', e) + return AnyType() + declared_self = args[0].variable.type + return analyze_member_access(name=e.name, typ=declared_self, node=e, is_lvalue=False, is_super=True, is_operator=False, builtin_type=self.named_type, not_ready_callback=self.not_ready_callback, msg=self.msg, override_info=base, chk=self.chk, - actual_self=declared_self) + original_type=declared_self) else: # Invalid super. This has been reported by the semantic analyzer. return AnyType() diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 9e61fa0dc096..54f6ecc353d5 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -33,28 +33,17 @@ def analyze_member_access(name: str, not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, override_info: TypeInfo = None, - actual_self: Type = None, + original_type: Type = None, chk: 'mypy.checker.TypeChecker' = None) -> Type: - """Analyse attribute access. + """Return the type of attribute `name` of typ. - This is a general operation that supports various different variations: - - 1. lvalue or non-lvalue access (i.e. setter or getter access) - 2. supertype access (when using super(); is_super == True and - override_info should refer to the supertype) - - actual_self is the type of E in the expression E.foo - the most precise - information available for mypy at the point of accessing E.foo - For example, - - class D(str): pass - a = D() - reveal_type(a.replace) - - during checking `a.replace`, typ will be str, whereas actual_self will be D. - This helps for error reporting and for implementing methods where self is generic. + original_type is the most precise inferred or declared type of the base object + that we have available. typ is generally a supertype of original_type. + When looking for an attribute of typ, we may perform recursive calls targeting + the fallback type, for example. + original_type is always the type used in the initial call. """ - actual_self = actual_self or typ + original_type = original_type or typ if isinstance(typ, Instance): if name == '__init__' and not is_super: # Accessing __init__ in statically typed code would compromise @@ -88,7 +77,7 @@ class D(str): pass # the first argument. pass else: - signature = bind_self(signature, actual_self) + signature = bind_self(signature, original_type) typ = map_instance_to_supertype(typ, method.info) return expand_type_by_instance(signature, typ) else: @@ -96,7 +85,7 @@ class D(str): pass return analyze_member_var_access(name, typ, info, node, is_lvalue, is_super, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) elif isinstance(typ, AnyType): # The base object has dynamic type. return AnyType() @@ -106,7 +95,7 @@ class D(str): pass # The only attribute NoneType has are those it inherits from object return analyze_member_access(name, builtin_type('builtins.object'), node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) elif isinstance(typ, UnionType): # The base object has dynamic type. msg.disable_type_names += 1 @@ -143,24 +132,24 @@ class D(str): pass # See https://github.com/python/mypy/pull/1787 for more info. result = analyze_class_attribute_access(ret_type, name, node, is_lvalue, builtin_type, not_ready_callback, msg, - actual_self=actual_self) + original_type=original_type) if result: return result # Look up from the 'type' type. return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) else: assert False, 'Unexpected type {}'.format(repr(ret_type)) elif isinstance(typ, FunctionLike): # Look up from the 'function' type. return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) elif isinstance(typ, TypeVarType): return analyze_member_access(name, typ.upper_bound, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) elif isinstance(typ, DeletedType): msg.deleted_as_rvalue(typ, node) return AnyType() @@ -176,17 +165,17 @@ class D(str): pass # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, builtin_type, not_ready_callback, msg, - actual_self=actual_self) + original_type=original_type) if result: return result fallback = builtin_type('builtins.type') return analyze_member_access(name, fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, - actual_self=actual_self, chk=chk) + original_type=original_type, chk=chk) if chk and chk.should_suppress_optional_error([typ]): return AnyType() - return msg.has_no_attr(actual_self, name, node) + return msg.has_no_attr(original_type, name, node) def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, @@ -194,15 +183,15 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, - actual_self: Type = None, + original_type: Type = None, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Analyse attribute access that does not target a method. This is logically part of analyze_member_access and the arguments are similar. - actual_self is the type of E in the expression E.var + original_type is the type of E in the expression E.var """ - actual_self = actual_self or itype + original_type = original_type or itype # It was not a method. Try looking up a variable. v = lookup_member_var_or_accessor(info, name, is_lvalue) @@ -219,7 +208,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, method = info.get_method('__getattr__') if method: function = function_type(method, builtin_type('builtins.function')) - bound_method = bind_self(function, actual_self) + bound_method = bind_self(function, original_type) typ = map_instance_to_supertype(itype, method.info) getattr_type = expand_type_by_instance(bound_method, typ) if isinstance(getattr_type, CallableType): @@ -235,20 +224,20 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, else: if chk and chk.should_suppress_optional_error([itype]): return AnyType() - return msg.has_no_attr(actual_self, name, node) + return msg.has_no_attr(original_type, name, node) def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, is_lvalue: bool, msg: MessageBuilder, not_ready_callback: Callable[[str, Context], None], - actual_self: Type = None) -> Type: + original_type: Type = None) -> Type: """Analyze access to an attribute via a Var node. This is conceptually part of analyze_member_access and the arguments are similar. - actual_self is the type of E in the expression E.var + original_type is the type of E in the expression E.var """ - actual_self = actual_self or itype + original_type = original_type or itype # Found a member variable. itype = map_instance_to_supertype(itype, var.info) typ = var.type @@ -273,7 +262,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # class. functype = t check_method_type(functype, itype, var.is_classmethod, node, msg) - signature = bind_self(functype, actual_self) + signature = bind_self(functype, original_type) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -349,8 +338,8 @@ def analyze_class_attribute_access(itype: Instance, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, - actual_self: Type = None) -> Type: - """actual_self is the type of E in the expression E.var""" + original_type: Type = None) -> Type: + """original_type is the type of E in the expression E.var""" node = itype.type.get(name) if not node: if itype.type.fallback_to_any: @@ -373,7 +362,7 @@ def analyze_class_attribute_access(itype: Instance, if isinstance(t, PartialType): return handle_partial_attribute_type(t, is_lvalue, msg, node.node) is_classmethod = is_decorated and cast(Decorator, node.node).func.is_class - return add_class_tvars(t, itype, is_classmethod, builtin_type, actual_self) + return add_class_tvars(t, itype, is_classmethod, builtin_type, original_type) elif isinstance(node.node, Var): not_ready_callback(name, context) return AnyType() @@ -394,7 +383,7 @@ def analyze_class_attribute_access(itype: Instance, def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool, builtin_type: Callable[[str], Instance], - actual_self: Type = None) -> Type: + original_type: Type = None) -> Type: """Instantiate type variables during analyze_class_attribute_access, e.g T and Q in the following: @@ -406,7 +395,7 @@ class B(A): pass B.foo() - actual_self is the value of the type B in the expression B.foo() + original_type is the value of the type B in the expression B.foo() """ # TODO: verify consistency betweem Q and T info = itype.type # type: TypeInfo @@ -415,11 +404,11 @@ class B(A): pass vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, actual_self if isinstance(actual_self, TypeType) else TypeType(itype)) + t = bind_self(t, original_type if isinstance(original_type, TypeType) else TypeType(itype)) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, - builtin_type, actual_self)) + builtin_type, original_type)) for i in t.items()]) return t @@ -541,15 +530,15 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo, F = TypeVar('F', bound=FunctionLike) -def bind_self(method: F, actual_self: Type = None) -> F: +def bind_self(method: F, original_type: Type = None) -> F: """Return a copy of `method`, with the type of its first parameter (usually - self or cls) bound to actual_self. + self or cls) bound to original_type. If the type of `self` is a generic type (T, or Type[T] for classmethods), - instantiate every occurrence of type with actual_self in the rest of the + instantiate every occurrence of type with original_type in the rest of the signature and in the return type. - actual_self is the type of E in the expression E.copy(). It is None in + original_type is the type of E in the expression E.copy(). It is None in compatibility checks. In this case we treat it as the erasure of the declared type of self. @@ -582,13 +571,13 @@ class B(A): pass if func.variables and (isinstance(self_param_type, TypeVarType) or (isinstance(self_param_type, TypeType) and isinstance(self_param_type.item, TypeVarType))): - if actual_self is None: + if original_type is None: # Type check method override # XXX value restriction as union? - actual_self = erase_to_bound(self_param_type) + original_type = erase_to_bound(self_param_type) typearg = infer_type_arguments([x.id for x in func.variables], - self_param_type, actual_self)[0] + self_param_type, original_type)[0] def expand(target: Type) -> Type: return expand_type(target, {func.variables[0].id: typearg}, False) From 48e139d1979c9f22fb5deead7bee4d5f29775f1a Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 21:54:13 +0300 Subject: [PATCH 44/47] test for erased types --- mypy/checkexpr.py | 1 - mypy/checkmember.py | 9 +++++---- mypy/expandtype.py | 10 ++++------ test-data/unit/check-selftype.test | 32 +++++++++++++++--------------- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f2597b0679b1..7b5a043293ed 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -31,7 +31,6 @@ from mypy import applytype from mypy import erasetype from mypy.checkmember import analyze_member_access, type_object_type -from mypy.semanal import fill_typevars from mypy.constraints import get_actual_type from mypy.checkstrformat import StringFormatterChecker from mypy.expandtype import expand_type diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 54f6ecc353d5..d08e2d7b790d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -37,9 +37,9 @@ def analyze_member_access(name: str, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Return the type of attribute `name` of typ. - original_type is the most precise inferred or declared type of the base object + original_type is the most precise inferred or declared type of the base object that we have available. typ is generally a supertype of original_type. - When looking for an attribute of typ, we may perform recursive calls targeting + When looking for an attribute of typ, we may perform recursive calls targeting the fallback type, for example. original_type is always the type used in the initial call. """ @@ -404,7 +404,8 @@ class B(A): pass vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, original_type if isinstance(original_type, TypeType) else TypeType(itype)) + t = bind_self(t, original_type if isinstance(original_type, TypeType) + else TypeType(itype)) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, @@ -580,7 +581,7 @@ class B(A): pass self_param_type, original_type)[0] def expand(target: Type) -> Type: - return expand_type(target, {func.variables[0].id: typearg}, False) + return expand_type(target, {func.variables[0].id: typearg}) arg_types = [expand(x) for x in func.arg_types[1:]] ret_type = expand(func.ret_type) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index c4871a23839e..7c00aa0cf347 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -7,12 +7,12 @@ ) -def expand_type(typ: Type, env: Dict[TypeVarId, Type], erase_instances: bool = True) -> Type: +def expand_type(typ: Type, env: Dict[TypeVarId, Type]) -> Type: """Substitute any type variable references in a type given by a type environment. """ - return typ.accept(ExpandTypeVisitor(env, erase_instances=erase_instances)) + return typ.accept(ExpandTypeVisitor(env)) def expand_type_by_instance(typ: Type, instance: Instance) -> Type: @@ -32,11 +32,9 @@ class ExpandTypeVisitor(TypeVisitor[Type]): """Visitor that substitutes type variables with values.""" variables = None # type: Dict[TypeVarId, Type] # TypeVar id -> TypeVar value - erase_instances = True # type: bool - def __init__(self, variables: Dict[TypeVarId, Type], erase_instances=True) -> None: + def __init__(self, variables: Dict[TypeVarId, Type]) -> None: self.variables = variables - self.erase_instances = erase_instances def visit_unbound_type(self, t: UnboundType) -> Type: return t @@ -76,7 +74,7 @@ def visit_type_var(self, t: TypeVarType) -> Type: inst = repl # Return copy of instance with type erasure flag on. return Instance(inst.type, inst.args, line=inst.line, - column=inst.column, erased=self.erase_instances) + column=inst.column, erased=True) else: return repl diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 37b150c7ef29..9c521244e8ea 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -9,10 +9,10 @@ class A: class B(A): pass -reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A' -reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B' -reveal_type(A().copy()) # E: Revealed type is '__main__.A' -reveal_type(B().copy()) # E: Revealed type is '__main__.B' +reveal_type(A().copy) # E: Revealed type is 'def () -> __main__.A*' +reveal_type(B().copy) # E: Revealed type is 'def () -> __main__.B*' +reveal_type(A().copy()) # E: Revealed type is '__main__.A*' +reveal_type(B().copy()) # E: Revealed type is '__main__.B*' [builtins fixtures/bool.pyi] @@ -101,10 +101,10 @@ class C: return cls() # E: Too few arguments for "C" -reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A' -reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B' -reveal_type(A.new()) # E: Revealed type is '__main__.A' -reveal_type(B.new()) # E: Revealed type is '__main__.B' +reveal_type(A.new) # E: Revealed type is 'def () -> __main__.A*' +reveal_type(B.new) # E: Revealed type is 'def () -> __main__.B*' +reveal_type(A.new()) # E: Revealed type is '__main__.A*' +reveal_type(B.new()) # E: Revealed type is '__main__.B*' [builtins fixtures/classmethod.pyi] @@ -123,10 +123,10 @@ Q = TypeVar('Q', bound='C', covariant=True) class C(A): def copy(self: Q) -> Q: pass -reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C' -reveal_type(C().copy()) # E: Revealed type is '__main__.C' -reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A' -reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A' +reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C*' +reveal_type(C().copy()) # E: Revealed type is '__main__.C*' +reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A*' +reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A*' [builtins fixtures/bool.pyi] @@ -167,10 +167,10 @@ class A: class B(A): pass -reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' -reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A) -> __main__.A) -> __main__.A' -reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B) -> __main__.B) -> __main__.B' +reveal_type(A().copy) # E: Revealed type is 'def (factory: def (__main__.A*) -> __main__.A*) -> __main__.A*' +reveal_type(B().copy) # E: Revealed type is 'def (factory: def (__main__.B*) -> __main__.B*) -> __main__.B*' +reveal_type(A.new) # E: Revealed type is 'def (factory: def (__main__.A*) -> __main__.A*) -> __main__.A*' +reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B*) -> __main__.B*) -> __main__.B*' [builtins fixtures/classmethod.pyi] From 25b84f8ad12e71272b328eb4493fae2bcb3d4d0d Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 26 Oct 2016 22:08:32 +0300 Subject: [PATCH 45/47] lint --- mypy/checkmember.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d08e2d7b790d..869e516cd9bc 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -404,8 +404,9 @@ class B(A): pass vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance) for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)] if is_classmethod: - t = bind_self(t, original_type if isinstance(original_type, TypeType) - else TypeType(itype)) + if not isinstance(original_type, TypeType): + original_type = TypeType(itype) + t = bind_self(t, original_type) return t.copy_modified(variables=vars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod, From 8944ff1de625609fb1e96c5f23a1b6fed54a1e02 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 27 Oct 2016 19:30:07 +0300 Subject: [PATCH 46/47] Revert accidental deletion from doc --- mypy/checkmember.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 869e516cd9bc..3494f5c37d73 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -37,6 +37,12 @@ def analyze_member_access(name: str, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Return the type of attribute `name` of typ. + This is a general operation that supports various different variations: + + 1. lvalue or non-lvalue access (i.e. setter or getter access) + 2. supertype access (when using super(); is_super == True and + override_info should refer to the supertype) + original_type is the most precise inferred or declared type of the base object that we have available. typ is generally a supertype of original_type. When looking for an attribute of typ, we may perform recursive calls targeting From 9221001295c0c5c4e1d52eec538c4d2177862cb1 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 27 Oct 2016 19:37:05 +0300 Subject: [PATCH 47/47] add warning for unsafe testcase --- test-data/unit/check-selftype.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 9c521244e8ea..d7b823695ae5 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -154,6 +154,7 @@ from typing import TypeVar, Callable, Type T = TypeVar('T', bound='A', covariant=True) class A: + # TODO: This is potentially unsafe, as we use T in an argument type def copy(self: T, factory: Callable[[T], T]) -> T: return factory(self)