From 07de26b6cc681ec0379540a24448fec95d9a9ef6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 16:42:47 +0000 Subject: [PATCH 01/24] Start work --- mypy/checkmember.py | 9 ++++-- mypy/infer.py | 10 +++--- mypy/typeops.py | 14 +++++--- test-data/unit/check-generics.test | 16 ++++++++++ test-data/unit/check-selftype.test | 51 ++++++++++++++++++++++++++++-- 5 files changed, 87 insertions(+), 13 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d12bf3c5ca30..bf00a7f5b99e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -200,6 +200,8 @@ def analyze_instance_member_access(name: str, # the first argument. pass else: + if isinstance(signature, FunctionLike): + check_self_arg(signature, mx.self_type, False, mx.context, name, mx.msg) signature = bind_self(signature, mx.self_type) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) @@ -614,7 +616,7 @@ class A: selfarg = item.arg_types[0] if is_classmethod: dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) - if not subtypes.is_subtype(dispatched_arg_type, erase_to_bound(selfarg)): + if not subtypes.is_subtype(dispatched_arg_type, erase_typevars(selfarg)): msg.incompatible_self_argument(name, dispatched_arg_type, item, is_classmethod, context) @@ -702,7 +704,10 @@ def analyze_class_attribute_access(itype: Instance, is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) - result = add_class_tvars(get_proper_type(t), itype, isuper, is_classmethod, + t = get_proper_type(t) + if isinstance(t, FunctionLike): + check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) + result = add_class_tvars(t, itype, isuper, is_classmethod, mx.builtin_type, mx.self_type) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, diff --git a/mypy/infer.py b/mypy/infer.py index b7d0dca8b9b4..57c85b70d837 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -2,10 +2,10 @@ from typing import List, Optional, Sequence -from mypy.constraints import infer_constraints, infer_constraints_for_callable +from mypy.constraints import infer_constraints, infer_constraints_for_callable, SUBTYPE_OF from mypy.types import Type, TypeVarId, CallableType from mypy.solve import solve_constraints -from mypy.constraints import SUBTYPE_OF +from mypy.constraints import SUPERTYPE_OF def infer_function_type_arguments(callee_type: CallableType, @@ -36,8 +36,10 @@ def infer_function_type_arguments(callee_type: CallableType, def infer_type_arguments(type_var_ids: List[TypeVarId], - template: Type, actual: Type) -> List[Optional[Type]]: + template: Type, actual: Type, + is_supertype: bool = False) -> List[Optional[Type]]: # Like infer_function_type_arguments, but only match a single type # against a generic type. - constraints = infer_constraints(template, actual, SUBTYPE_OF) + constraints = infer_constraints(template, actual, + SUPERTYPE_OF if is_supertype else SUBTYPE_OF) return solve_constraints(type_var_ids, constraints) diff --git a/mypy/typeops.py b/mypy/typeops.py index 39c81617c9ab..35e8aec0e037 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -130,6 +130,10 @@ def map_type_from_supertype(typ: Type, return expand_type_by_instance(typ, inst_type) +def instance_or_var(typ: ProperType) -> bool: + return isinstance(typ, TypeVarType) or isinstance(typ, Instance) + + F = TypeVar('F', bound=FunctionLike) @@ -174,9 +178,9 @@ class B(A): pass # TODO: infer bounds on the type of *args? return cast(F, func) self_param_type = get_proper_type(func.arg_types[0]) - if func.variables and (isinstance(self_param_type, TypeVarType) or + if func.variables and (instance_or_var(self_param_type) or (isinstance(self_param_type, TypeType) and - isinstance(self_param_type.item, TypeVarType))): + instance_or_var(self_param_type.item))): if original_type is None: # Type check method override # XXX value restriction as union? @@ -184,13 +188,15 @@ class B(A): pass original_type = get_proper_type(original_type) ids = [x.id for x in func.variables] - typearg = get_proper_type(infer_type_arguments(ids, self_param_type, original_type)[0]) + typearg = get_proper_type(infer_type_arguments(ids, self_param_type, + original_type, is_supertype=True)[0]) if (is_classmethod and isinstance(typearg, UninhabitedType) and isinstance(original_type, (Instance, TypeVarType, TupleType))): # In case we call a classmethod through an instance x, fallback to type(x) # TODO: handle Union typearg = get_proper_type(infer_type_arguments(ids, self_param_type, - TypeType(original_type))[0]) + TypeType(original_type), + is_supertype=True)[0]) def expand(target: Type) -> Type: assert typearg is not None diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 0441f26cee36..d99bb706eff1 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1948,6 +1948,22 @@ class B(A[T], Generic[T, S]): reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__.B*[T`1, S`2]]' [builtins fixtures/classmethod.pyi] +[case testGenericClassAlternativeConstructorPrecise] +from typing import Generic, TypeVar, Type + +T = TypeVar('T') +Q = TypeVar('Q') + +class Base(Generic[T]): + @classmethod + def make_some(cls: Type[Q], item: T, count: int) -> Q: ... +class Sub(Base[T]): + ... + +reveal_type(Sub.make_some('yes', 3)) # N: Revealed type is '__main__.Sub[builtins.str*]' +Sub[int].make_some('no', 3) # E: Argument 1 to "make_some" of "Base" has incompatible type "str"; expected "int" +[builtins fixtures/classmethod.pyi] + [case testGenericClassAttrUnboundOnClass] from typing import Generic, TypeVar T = TypeVar('T') diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 80455141c82e..258cba960027 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -468,16 +468,61 @@ class B(A[Q]): a: A[int] b: B[str] reveal_type(a.g) # N: Revealed type is 'builtins.int' ---reveal_type(a.gt) # N: Revealed type is 'builtins.int' +reveal_type(a.gt) # N: Revealed type is 'builtins.int*' reveal_type(a.f()) # N: Revealed type is 'builtins.int' reveal_type(a.ft()) # N: Revealed type is '__main__.A*[builtins.int]' reveal_type(b.g) # N: Revealed type is 'builtins.int' ---reveal_type(b.gt) # N: Revealed type is '__main__.B*[builtins.str]' +reveal_type(b.gt) # N: Revealed type is 'builtins.str*' reveal_type(b.f()) # N: Revealed type is 'builtins.int' reveal_type(b.ft()) # N: Revealed type is '__main__.B*[builtins.str]' - [builtins fixtures/property.pyi] +[case testSelfTypeRestrictedMethod] +from typing import TypeVar, Generic + +T = TypeVar('T') +class C(Generic[T]): + def from_item(self: C[str]) -> None: ... + +i: C[int] +s: C[str] + +i.from_item() # E: Invalid self argument "C[int]" to attribute function "from_item" with type "Callable[[C[str]], None]" +s.from_item() + +[case testSelfTypeRestrictedClassMethod] +from typing import TypeVar, Generic, Type + +T = TypeVar('T') +class C(Generic[T]): + @classmethod + def from_item(cls: Type[C[str]]) -> None: ... + +class DI(C[int]): ... +class DS(C[str]): ... + +DI().from_item() # E: Invalid self argument "Type[DI]" to class attribute function "from_item" with type "Callable[[Type[C[str]]], None]" +DS().from_item() +DI.from_item() # E: Invalid self argument "Type[DI]" to attribute function "from_item" with type "Callable[[Type[C[str]]], None]" +DS.from_item() +[builtins fixtures/classmethod.pyi] + +[case testSelfTypeNarrowBinding] +from typing import TypeVar, List, Generic + +T = TypeVar('T') +S = TypeVar('S') + +class Base(Generic[T]): + def get_item(self: Base[List[S]]) -> S: ... + +class Sub(Base[List[int]]): ... +class BadSub(Base[int]): ... + +reveal_type(Sub().get_item()) # N: Revealed type is 'builtins.int' +BadSub().get_item() # E: Invalid self argument "BadSub" to attribute function "get_item" with type "Callable[[Base[List[S]]], S]" +[builtins fixtures/list.pyi] + [case testSelfTypeNotSelfType] # Friendlier error messages for common mistakes. See #2950 class A: From 56c5b34f016067cd573119529105e92705cdf25c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 17:57:58 +0000 Subject: [PATCH 02/24] Some polish --- mypy/checkmember.py | 7 ++++--- mypy/typeops.py | 3 ++- test-data/unit/check-classes.test | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index bf00a7f5b99e..d34694485acd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -200,8 +200,9 @@ def analyze_instance_member_access(name: str, # the first argument. pass else: - if isinstance(signature, FunctionLike): - check_self_arg(signature, mx.self_type, False, mx.context, name, mx.msg) + if isinstance(signature, FunctionLike) and name != '__call__': + dispatched_type = meet.meet_types(mx.original_type, typ) + check_self_arg(signature, dispatched_type, False, mx.context, name, mx.msg) signature = bind_self(signature, mx.self_type) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) @@ -705,7 +706,7 @@ def analyze_class_attribute_access(itype: Instance, is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) t = get_proper_type(t) - if isinstance(t, FunctionLike): + if isinstance(t, FunctionLike) and is_classmethod: check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) result = add_class_tvars(t, itype, isuper, is_classmethod, mx.builtin_type, mx.self_type) diff --git a/mypy/typeops.py b/mypy/typeops.py index 35e8aec0e037..e178e25a30e4 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -131,7 +131,8 @@ def map_type_from_supertype(typ: Type, def instance_or_var(typ: ProperType) -> bool: - return isinstance(typ, TypeVarType) or isinstance(typ, Instance) + return (isinstance(typ, TypeVarType) or + isinstance(typ, Instance) and typ != fill_typevars(typ.type)) F = TypeVar('F', bound=FunctionLike) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a208e9ef09e1..5a039a4aa4c1 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4286,7 +4286,7 @@ reveal_type(ta.g3) # N: Revealed type is 'def () -> Type[__main__.A]' reveal_type(ta.g4) # N: Revealed type is 'def () -> Type[__main__.A]' x: M = ta -x.g1 # should be error: Argument 0 to "g1" of "M" has incompatible type "M"; expected "Type[A]" +x.g1 # E: Invalid self argument "M" to attribute function "g1" with type "Callable[[Type[A]], A]" x.g2 # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected "Type[TA]" x.g3 # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA" reveal_type(x.g4) # N: Revealed type is 'def () -> __main__.M*' From 5870ce9efbf0af2d83ef5313971dddf0800e952e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 19:28:24 +0000 Subject: [PATCH 03/24] Add some overloads --- mypy/checkmember.py | 34 +++++++++++++++------- mypy/typeops.py | 24 ++++++++++++---- test-data/unit/check-selftype.test | 45 ++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d34694485acd..fb7858083bac 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -202,7 +202,8 @@ def analyze_instance_member_access(name: str, else: if isinstance(signature, FunctionLike) and name != '__call__': dispatched_type = meet.meet_types(mx.original_type, typ) - check_self_arg(signature, dispatched_type, False, mx.context, name, mx.msg) + signature = check_self_arg(signature, dispatched_type, False, mx.context, + name, mx.msg) signature = bind_self(signature, mx.self_type) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) @@ -549,8 +550,8 @@ def analyze_var(name: str, # In `x.f`, when checking `x` against A1 we assume x is compatible with A # and similarly for B1 when checking agains B dispatched_type = meet.meet_types(mx.original_type, itype) - check_self_arg(functype, dispatched_type, var.is_classmethod, mx.context, name, - mx.msg) + functype = check_self_arg(functype, dispatched_type, var.is_classmethod, + mx.context, name, mx.msg) signature = bind_self(functype, mx.self_type, var.is_classmethod) if var.is_property: # A property cannot have an overloaded type => the cast is fine. @@ -599,7 +600,7 @@ def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, context: Context, name: str, - msg: MessageBuilder) -> None: + msg: MessageBuilder) -> FunctionLike: """For x.f where A.f: A1 -> T, check that meet(type(x), A) <: A1 for each overload. dispatched_arg_type is meet(B, A) in the following example @@ -608,18 +609,31 @@ def g(x: B): x.f class A: f: Callable[[A1], None] """ - # TODO: this is too strict. We can return filtered overloads for matching definitions - for item in functype.items(): + items = functype.items() + if not items: + return functype + new_items = [] + for item in items: if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). msg.no_formal_self(name, item, context) + # This is pretty bad, so just return the original signature if + # there is at least one such error. + return functype else: selfarg = item.arg_types[0] if is_classmethod: dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) - if not subtypes.is_subtype(dispatched_arg_type, erase_typevars(selfarg)): - msg.incompatible_self_argument(name, dispatched_arg_type, item, - is_classmethod, context) + if subtypes.is_subtype(dispatched_arg_type, erase_typevars(selfarg)): + new_items.append(item) + if not new_items: + # Choose first item for the message (it may be not very helpful for overloads). + msg.incompatible_self_argument(name, dispatched_arg_type, items[0], + is_classmethod, context) + return functype + if len(new_items) == 1: + return new_items[0] + return Overloaded(new_items) def analyze_class_attribute_access(itype: Instance, @@ -707,7 +721,7 @@ def analyze_class_attribute_access(itype: Instance, or (isinstance(node.node, FuncBase) and node.node.is_class)) t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: - check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) + t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) result = add_class_tvars(t, itype, isuper, is_classmethod, mx.builtin_type, mx.self_type) if not mx.is_lvalue: diff --git a/mypy/typeops.py b/mypy/typeops.py index e178e25a30e4..9b03f22334cb 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -9,13 +9,13 @@ from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, - TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, NoneType, + TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types, copy_type ) from mypy.nodes import ( FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, Expression, - StrExpr + StrExpr, ARG_POS ) from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance, expand_type @@ -50,6 +50,15 @@ def type_object_type_from_function(signature: FunctionLike, # class B(A[List[T]], Generic[T]): pass # # We need to first map B's __init__ to the type (List[T]) -> None. + + # We first record all non-trivial (explicit) self types. + if not is_new and def_info == info: + orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_kinds[0] == ARG_POS and + it.arg_types[0] != fill_typevars(def_info) else None) + for it in signature.items()] + else: + orig_self_types = [None] * len(signature.items()) + signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new) signature = cast(FunctionLike, map_type_from_supertype(signature, info, def_info)) @@ -59,19 +68,19 @@ def type_object_type_from_function(signature: FunctionLike, special_sig = 'dict' if isinstance(signature, CallableType): - return class_callable(signature, info, fallback, special_sig, is_new) + return class_callable(signature, info, fallback, special_sig, is_new, orig_self_types[0]) else: # Overloaded __init__/__new__. assert isinstance(signature, Overloaded) items = [] # type: List[CallableType] - for item in signature.items(): - items.append(class_callable(item, info, fallback, special_sig, is_new)) + for item, orig_self in zip(signature.items(), orig_self_types): + items.append(class_callable(item, info, fallback, special_sig, is_new, orig_self)) return Overloaded(items) def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, special_sig: Optional[str], - is_new: bool) -> CallableType: + is_new: bool, orig_self_type: Optional[Type] = None) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] variables.extend(info.defn.type_vars) @@ -89,6 +98,8 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, and is_subtype(init_ret_type, default_ret_type, ignore_type_params=True) ): ret_type = init_ret_type # type: Type + elif orig_self_type is not None: + ret_type = orig_self_type else: ret_type = default_ret_type @@ -131,6 +142,7 @@ def map_type_from_supertype(typ: Type, def instance_or_var(typ: ProperType) -> bool: + # TODO: use more principled check for non-trivial self-types. return (isinstance(typ, TypeVarType) or isinstance(typ, Instance) and typ != fill_typevars(typ.type)) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 258cba960027..cb209e5528e4 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -507,6 +507,51 @@ DI.from_item() # E: Invalid self argument "Type[DI]" to attribute function "fro DS.from_item() [builtins fixtures/classmethod.pyi] +[case testSelfTypeRestrictedMethodOverload] +from typing import TypeVar, Generic, overload, Tuple + +T = TypeVar('T') +class C(Generic[T]): + @overload + def from_item(self: C[str], item: str) -> None: ... + @overload + def from_item(self: C[int], item: Tuple[int]) -> None: ... + def from_item(self, item): + ... + +ci: C[int] +cs: C[str] +reveal_type(ci.from_item) # N: Revealed type is 'def (item: Tuple[builtins.int])' +reveal_type(cs.from_item) # N: Revealed type is 'def (item: builtins.str)' + +[case testSelfTypeRestrictedMethodOverloadInit] +from lib import P, C + +reveal_type(P) # N: Revealed type is 'Overload(def [T] (use_str: Literal[True]) -> lib.P[builtins.str], def [T] (use_str: Literal[False]) -> lib.P[builtins.int])' +reveal_type(P(use_str=True)) # N: Revealed type is 'lib.P[builtins.str]' +reveal_type(P(use_str=False)) # N: Revealed type is 'lib.P[builtins.int]' + +reveal_type(C) # N: Revealed type is 'Overload(def [T] (item: T`1, use_tuple: Literal[False]) -> lib.C[T`1], def [T] (item: T`1, use_tuple: Literal[True]) -> lib.C[builtins.tuple[T`1]])' +reveal_type(C(0, use_tuple=False)) # N: Revealed type is 'lib.C[builtins.int*]' +reveal_type(C(0, use_tuple=True)) # N: Revealed type is 'lib.C[builtins.tuple[builtins.int*]]' + +[file lib.pyi] +from typing import TypeVar, Generic, overload, Tuple +from typing_extensions import Literal + +T = TypeVar('T') +class P(Generic[T]): + @overload + def __init__(self: P[str], use_str: Literal[True]) -> None: ... + @overload + def __init__(self: P[int], use_str: Literal[False]) -> None: ... + +class C(Generic[T]): + @overload + def __init__(self: C[T], item: T, use_tuple: Literal[False]) -> None: ... + @overload + def __init__(self: C[Tuple[T, ...]], item: T, use_tuple: Literal[True]) -> None: ... + [case testSelfTypeNarrowBinding] from typing import TypeVar, List, Generic From 082f4f44953b5b9ec18cf6770bfaf309ed7c2a58 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 19:52:39 +0000 Subject: [PATCH 04/24] Some fixes --- mypy/typeops.py | 2 +- test-data/unit/check-generics.test | 12 +++++++----- test-data/unit/check-newsemanal.test | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 9b03f22334cb..428ab52d1408 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -52,7 +52,7 @@ def type_object_type_from_function(signature: FunctionLike, # We need to first map B's __init__ to the type (List[T]) -> None. # We first record all non-trivial (explicit) self types. - if not is_new and def_info == info: + if not is_new and def_info == info and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_kinds[0] == ARG_POS and it.arg_types[0] != fill_typevars(def_info) else None) for it in signature.items()] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index d99bb706eff1..e648a4f12448 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1949,19 +1949,21 @@ reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__. [builtins fixtures/classmethod.pyi] [case testGenericClassAlternativeConstructorPrecise] -from typing import Generic, TypeVar, Type +from typing import Generic, TypeVar, Type, Tuple, Any T = TypeVar('T') -Q = TypeVar('Q') +Q = TypeVar('Q', bound=Base[Any]) class Base(Generic[T]): + def __init__(self, item: T) -> None: ... @classmethod - def make_some(cls: Type[Q], item: T, count: int) -> Q: ... + def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: + return cls(item), cls(item) class Sub(Base[T]): ... -reveal_type(Sub.make_some('yes', 3)) # N: Revealed type is '__main__.Sub[builtins.str*]' -Sub[int].make_some('no', 3) # E: Argument 1 to "make_some" of "Base" has incompatible type "str"; expected "int" +reveal_type(Sub.make_pair('yes')) # N: Revealed type is 'Tuple[__main__.Sub[builtins.str*], __main__.Sub[builtins.str*]]' +Sub[int].make_pair('no') # E: Argument 1 to "make_pair" of "Base" has incompatible type "str"; expected "int" [builtins fixtures/classmethod.pyi] [case testGenericClassAttrUnboundOnClass] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 8e2dcc72e8fd..664288c9ff20 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3173,4 +3173,4 @@ class User: def __init__(self, name: str) -> None: self.name = name # E: Cannot assign to a method \ - # E: Incompatible types in assignment (expression has type "str", variable has type overloaded function) + # E: Incompatible types in assignment (expression has type "str", variable has type "Callable[..., Any]") From 869e15e4e7fe10c7ffac43e071f258d83c2521c6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 21:54:32 +0000 Subject: [PATCH 05/24] Add some mixin fun --- docs/source/generics.rst | 2 + docs/source/more_types.rst | 119 +++++++++++++++++++++++++++++ mypy/checker.py | 11 ++- mypy/checkmember.py | 2 +- test-data/unit/check-classes.test | 4 +- test-data/unit/check-selftype.test | 69 +++++++++++++++++ 6 files changed, 200 insertions(+), 7 deletions(-) diff --git a/docs/source/generics.rst b/docs/source/generics.rst index e23433af59a1..937b6ae51ecc 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -312,6 +312,8 @@ or a deserialization method returns the actual type of self. Therefore you may need to silence mypy inside these methods (but not at the call site), possibly by making use of the ``Any`` type. +For some advanced uses of self-types see :ref:`additional examples `. + .. _variance-of-generics: Variance of generic types diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 8494de1aefee..75c8187a4ce6 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -561,6 +561,125 @@ with ``Union[int, slice]`` and ``Union[T, Sequence]``. to returning ``Any`` only if the input arguments also contain ``Any``. +.. _advanced_self: + +Advanced uses of self-types +*************************** + +Normally, mypy doesn't require annotation for first argument of instance and +class methods. However, they may be needed to have more precise static typing +for certain programming patterns. + +Restricted methods in generic classes +------------------------------------- + +In generic classes some methods may be allowed to call only +for certain values of type arguments: + +.. code-block:: python + + T = TypeVar('T') + class Tag(Generic[T]): + item: T + def uppercase_item(self: C[str]) -> str: + return self.item.upper() + + def label(ti: Tag[int], ts: Tag[str]) -> None: + ti.uppercase_item() # E: Invalid self argument "Tag[int]" to attribute function + # "uppercase_item" with type "Callable[[Tag[str]], str]" + ts.uppercase_item() # This is OK + +This pattern also allows extraction of items in situations where type +argument is itself generic: + +.. code-block:: python + + T = TypeVar('T') + S = TypeVar('S') + + class Node(Generic[T]): + def __init__(self, content: T) -> None: + self.content = content + def first_item(self: Node[Sequence[S]]) -> S: + return self.content[0] + + page: Node[List[str]] + page.get_first_item() # OK, type is "str" + + Node(0).get_first_item() # Error: Invalid self argument "Node[int]" to attribute function + # "first_item" with type "Callable[[Node[Sequence[S]]], S]" + +Finally, one can use overloads on self-type to express precise types of +some tricky methods: + +.. code-block:: python + + T = TypeVar('T') + + class Tag(Generic[T]): + @overload + def export(self: Tag[str]) -> str: ... + @overload + def export(self, converter: Callable[[T], str]) -> T: ... + + def export(self, converter=None): + if isinstance(self.item, str): + return self.item + return converter(self.item) + +Mixin classes +------------- + +Using host class protocol as a self-type in mixin methods allows +static typing of mixin class patter: + +.. code-block:: python + + class Resource(Protocol): + def close(self) -> int: ... + + class AtomicClose: + def atomic_close(self: Resource) -> int: + with Lock(): + return self.close() + + class File(AtomicClose): + def close(self) -> int: + ... + + class Bad(AtomicClose): + ... + + f: File + b: Bad + f.atomic_close() # OK + b.atomic_close() # Error: Invalid self type for "atomic_close" + +Precise typing of alternative constructors +------------------------------------------ + +Some classes may define alternative constructors. If these +classes are generic, self-type allows giving them precise signatures: + +.. code-block:: python + + T = TypeVar('T') + Q = TypeVar('Q', bound=Base[Any]) + + class Base(Generic[T]): + def __init__(self, item: T) -> None: + self.item = item + @classmethod + def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: + return cls(item), cls(item) + + class Sub(Base[T]): + ... + + pair = Sub.make_pair('yes') # Type is "Tuple[Sub[str], Sub[str]]" + bad = Sub[int].make_pair('no') # Error: Argument 1 to "make_pair" of "Base" + # has incompatible type "str"; expected "int" + .. _async-and-await: Typing async/await diff --git a/mypy/checker.py b/mypy/checker.py index 7cc1b04b5d91..c7bdcbfcab86 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -909,7 +909,9 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) erased = erase_to_bound(arg_type) if not is_subtype_ignoring_tvars(ref_type, erased): note = None - if typ.arg_names[i] in ['self', 'cls']: + if isinstance(erased, Instance) and erased.type.is_protocol: + msg = None + elif typ.arg_names[i] in ['self', 'cls']: if (self.options.python_version[0] < 3 and is_same_type(erased, arg_type) and not isclass): msg = message_registry.INVALID_SELF_TYPE_OR_EXTRA_ARG @@ -919,9 +921,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) erased, ref_type) else: msg = message_registry.MISSING_OR_INVALID_SELF_TYPE - self.fail(msg, defn) - if note: - self.note(note, defn) + if msg: + self.fail(msg, defn) + if note: + self.note(note, defn) elif isinstance(arg_type, TypeVarType): # Refuse covariant parameter type variables # TODO: check recursively for inner type variables diff --git a/mypy/checkmember.py b/mypy/checkmember.py index fb7858083bac..ff52416f8544 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -624,7 +624,7 @@ class A: selfarg = item.arg_types[0] if is_classmethod: dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) - if subtypes.is_subtype(dispatched_arg_type, erase_typevars(selfarg)): + if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))): new_items.append(item) if not new_items: # Choose first item for the message (it may be not very helpful for overloads). diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 5a039a4aa4c1..1ce653deeefc 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4287,8 +4287,8 @@ reveal_type(ta.g4) # N: Revealed type is 'def () -> Type[__main__.A]' x: M = ta x.g1 # E: Invalid self argument "M" to attribute function "g1" with type "Callable[[Type[A]], A]" -x.g2 # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected "Type[TA]" -x.g3 # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA" +x.g2 # E: Invalid self argument "M" to attribute function "g2" with type "Callable[[Type[TA]], TA]" +x.g3 # E: Invalid self argument "M" to attribute function "g3" with type "Callable[[TTA], TTA]" reveal_type(x.g4) # N: Revealed type is 'def () -> __main__.M*' def r(ta: Type[TA], tta: TTA) -> None: diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index cb209e5528e4..2b6e552549db 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -524,6 +524,28 @@ cs: C[str] reveal_type(ci.from_item) # N: Revealed type is 'def (item: Tuple[builtins.int])' reveal_type(cs.from_item) # N: Revealed type is 'def (item: builtins.str)' +[case testSelfTypeRestrictedMethodOverloadFallback] +from typing import TypeVar, Generic, overload, Callable + +T = TypeVar('T') +class C(Generic[T]): + @overload + def from_item(self: C[str]) -> str: ... + @overload + def from_item(self, converter: Callable[[T], str]) -> str: ... + def from_item(self, converter): + ... + +ci: C[int] +cs: C[str] +reveal_type(cs.from_item()) # N: Revealed type is 'builtins.str' +ci.from_item() # E: Too few arguments for "from_item" of "C" + +def conv(x: int) -> str: ... +def bad(x: str) -> str: ... +reveal_type(ci.from_item(conv)) # N: Revealed type is 'builtins.str' +ci.from_item(bad) # E: Argument 1 to "from_item" of "C" has incompatible type "Callable[[str], str]"; expected "Callable[[int], str]" + [case testSelfTypeRestrictedMethodOverloadInit] from lib import P, C @@ -568,6 +590,53 @@ reveal_type(Sub().get_item()) # N: Revealed type is 'builtins.int' BadSub().get_item() # E: Invalid self argument "BadSub" to attribute function "get_item" with type "Callable[[Base[List[S]]], S]" [builtins fixtures/list.pyi] +[case testMixinAllowedWithProtocol] +from typing import TypeVar +from typing_extensions import Protocol + +class Resource(Protocol): + def close(self) -> int: ... + +class AtomicClose: + def atomic_close(self: Resource) -> int: + return self.close() + +T = TypeVar('T', bound=Resource) +class Copyable: + def copy(self: T) -> T: ... + +class File(AtomicClose, Copyable): + def close(self) -> int: + ... + +class Bad(AtomicClose, Copyable): + ... + +f: File +b: Bad +f.atomic_close() # OK +b.atomic_close() # E: Invalid self argument "Bad" to attribute function "atomic_close" with type "Callable[[Resource], int]" + +reveal_type(f.copy()) # N: Revealed type is '__main__.File*' +b.copy() # E: Invalid self argument "Bad" to attribute function "copy" with type "Callable[[T], T]" + +[case testBadClassLevelDecoratorHack] +from typing_extensions import Protocol +from typing import TypeVar, Any + +class FuncLike(Protocol): + __call__: Any +F = TypeVar('F', bound=FuncLike) + +class Test: + def _deco(func: F) -> F: ... + + @_deco + def meth(self, x: str) -> int: ... + +reveal_type(Test().meth) # N: Revealed type is 'def (x: builtins.str) -> builtins.int' +Test()._deco # E: Invalid self argument "Test" to attribute function "_deco" with type "Callable[[F], F]" + [case testSelfTypeNotSelfType] # Friendlier error messages for common mistakes. See #2950 class A: From a6060da6d598a7170fad5e98c5e4674f06260cbc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 21:58:46 +0000 Subject: [PATCH 06/24] Fix self-check --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index c7bdcbfcab86..1d1d7533c5e1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -906,7 +906,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) isclass = defn.is_class or defn.name() in ('__new__', '__init_subclass__') if isclass: ref_type = mypy.types.TypeType.make_normalized(ref_type) - erased = erase_to_bound(arg_type) + erased = get_proper_type(erase_to_bound(arg_type)) if not is_subtype_ignoring_tvars(ref_type, erased): note = None if isinstance(erased, Instance) and erased.type.is_protocol: From 6dc8126a4bdd0c7df53dc3e650c59a9eba1d68bf Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 22:27:13 +0000 Subject: [PATCH 07/24] Add one more TODO --- mypy/checkmember.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ff52416f8544..0bf7dff78a2a 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -201,9 +201,11 @@ def analyze_instance_member_access(name: str, pass else: if isinstance(signature, FunctionLike) and name != '__call__': + # TODO: use proper treatment of special methods on unions instead + # of this hack here and below (i.e. mx.self_type). dispatched_type = meet.meet_types(mx.original_type, typ) signature = check_self_arg(signature, dispatched_type, False, mx.context, - name, mx.msg) + name, mx.msg) signature = bind_self(signature, mx.self_type) typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) From 48bc5e3380f391ec55e5127391ad224f797c7046 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 2 Nov 2019 23:09:50 +0000 Subject: [PATCH 08/24] Add two more tricky tests --- test-data/unit/check-selftype.test | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 2b6e552549db..14e76e4ec329 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -637,6 +637,56 @@ class Test: reveal_type(Test().meth) # N: Revealed type is 'def (x: builtins.str) -> builtins.int' Test()._deco # E: Invalid self argument "Test" to attribute function "_deco" with type "Callable[[F], F]" +[case testSelfTypeTrickyExample] +from typing import * + +In = TypeVar('In') +Out = TypeVar('Out') +Mid = TypeVar('Mid') +NewOut = TypeVar('NewOut') + +class Lnk(Generic[In, Out]): + def test(self: Lnk[In, Mid], other: Lnk[Mid, NewOut]) -> Lnk[In, NewOut]: ... + +class X: pass +class Y: pass +class Z: pass + +a: Lnk[X, Y] = Lnk() +b: Lnk[Y, Z] = Lnk() + +a.test(b) +b.test(a) # E: Argument 1 to "test" of "Lnk" has incompatible type "Lnk[X, Y]"; expected "Lnk[Z, Y]" + +[case testSelfTypeReallyTrickyExample] +from typing import * + +In = TypeVar('In') +Out = TypeVar('Out') +Other = TypeVar('Other') + +_1 = TypeVar('_1') +_2 = TypeVar('_2') +__1 = TypeVar('__1') +__2 = TypeVar('__2') + +class Lnk(Generic[In, Out]): + @overload + def __rshift__(self, other: Lnk[Out, Other]) -> Lnk[In,Other]: ... + @overload + def __rshift__(self: Lnk[In, Tuple[_1, _2]], + other: Tuple[Lnk[_1, __1], Lnk[_2, __2]]) -> Lnk[In, Tuple[__1, __2]]: ... + def __rshift__(self: Any, other: Any) -> Any: + ... + +a: Lnk[str, Tuple[str, int]] = Lnk() +b: Lnk[str, int] = Lnk() +c: Lnk[int, float] = Lnk() + +d: Lnk[str, float] = b >> c # OK +e: Lnk[str, Tuple[int, float]] = a >> (b, c) # OK +f: Lnk[str, Tuple[float, int]] = a >> (c, b) # E: Unsupported operand types for >> ("Lnk[str, Tuple[str, int]]" and "Tuple[Lnk[int, float], Lnk[str, int]]") + [case testSelfTypeNotSelfType] # Friendlier error messages for common mistakes. See #2950 class A: From f287273536ca4deb29037edc30ede646dafd2203 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:36:02 +0000 Subject: [PATCH 09/24] Fix a corner case for Type[...] vs metaclass; add tests --- mypy/checker.py | 5 +++- mypy/meet.py | 4 +++ test-data/unit/check-selftype.test | 44 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1d1d7533c5e1..0047f845eda9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -909,7 +909,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) erased = get_proper_type(erase_to_bound(arg_type)) if not is_subtype_ignoring_tvars(ref_type, erased): note = None - if isinstance(erased, Instance) and erased.type.is_protocol: + if (isinstance(erased, Instance) and erased.type.is_protocol or + isinstance(erased, TypeType) and + isinstance(erased.item, Instance) and + erased.item.type.is_protocol): msg = None elif typ.arg_names[i] in ['self', 'cls']: if (self.options.python_version[0] < 3 diff --git a/mypy/meet.py b/mypy/meet.py index 192f79dde8af..517eb93a5c81 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -493,6 +493,10 @@ def visit_instance(self, t: Instance) -> ProperType: call = unpack_callback_protocol(t) if call: return meet_types(call, self.s) + elif isinstance(self.s, FunctionLike) and self.s.is_type_obj() and t.type.is_metaclass(): + if is_subtype(self.s.fallback, t): + return self.s + return self.default(self.s) elif isinstance(self.s, TypeType): return meet_types(t, self.s) elif isinstance(self.s, TupleType): diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 14e76e4ec329..3761ad4c7928 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -687,6 +687,50 @@ d: Lnk[str, float] = b >> c # OK e: Lnk[str, Tuple[int, float]] = a >> (b, c) # OK f: Lnk[str, Tuple[float, int]] = a >> (c, b) # E: Unsupported operand types for >> ("Lnk[str, Tuple[str, int]]" and "Tuple[Lnk[int, float], Lnk[str, int]]") +[case testSelfTypeStructureMetaclassMatch] +from typing import TypeVar, Type, Generic, cast + +Cls = TypeVar('Cls') +T = TypeVar('T') + +class Manager(Generic[Cls]): + def create(self: Manager[Type[T]]) -> T: ... + +class ModelMeta(type): + @property + def objects(cls: T) -> Manager[T]: ... + +class Model(metaclass=ModelMeta): + pass + +class Dog(Model): ... +class Cat(Model): ... + +c: Cat = Dog.objects.create() # E: Incompatible types in assignment (expression has type "Dog", variable has type "Cat") +d: Dog = Dog.objects.create() +[builtins fixtures/property.pyi] + +[case testSelfTypeProtocolMetaclassMatch] +from typing import Type, TypeVar, Protocol + +class HasX(Protocol): + x: int + +T = TypeVar('T', bound=HasX) + +class Meta(type): + def do_x(cls: Type[T]) -> T: + cls.x + return cls() + +class Good(metaclass=Meta): + x: int +class Bad(metaclass=Meta): + pass + +Good.do_x() +Bad.do_x() # E: error + [case testSelfTypeNotSelfType] # Friendlier error messages for common mistakes. See #2950 class A: From e696834d996a99b0fce64f3f1093db35ebd6017e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:47:41 +0000 Subject: [PATCH 10/24] Update tests --- test-data/unit/check-classes.test | 6 +++--- test-data/unit/check-selftype.test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1ce653deeefc..0764ee3e4903 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4271,9 +4271,9 @@ reveal_type(A.g4) # N: Revealed type is 'def () -> def () -> __main__.A' class B(metaclass=M): def foo(self): pass -B.g1 # Should be error: Argument 0 to "g1" of "M" has incompatible type "B"; expected "Type[A]" -B.g2 # Should be error: Argument 0 to "g2" of "M" has incompatible type "B"; expected "Type[TA]" -B.g3 # Should be error: Argument 0 to "g3" of "M" has incompatible type "B"; expected "TTA" +B.g1 # E: Invalid self argument "Type[B]" to attribute function "g1" with type "Callable[[Type[A]], A]" +B.g2 # E: Invalid self argument "Type[B]" to attribute function "g2" with type "Callable[[Type[TA]], TA]" +B.g3 # E: Invalid self argument "Type[B]" to attribute function "g3" with type "Callable[[TTA], TTA]" reveal_type(B.g4) # N: Revealed type is 'def () -> def () -> __main__.B' # 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 3761ad4c7928..f85161498902 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -729,7 +729,7 @@ class Bad(metaclass=Meta): pass Good.do_x() -Bad.do_x() # E: error +Bad.do_x() # E: Invalid self argument "Type[Bad]" to attribute function "do_x" with type "Callable[[Type[T]], T]" [case testSelfTypeNotSelfType] # Friendlier error messages for common mistakes. See #2950 From 72b9c8a793baf7faf499d5baa1e9b7d2ee17edcc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:47:57 +0000 Subject: [PATCH 11/24] Update docs/source/more_types.rst Co-Authored-By: Michael Lee --- docs/source/more_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 75c8187a4ce6..e3a406ac47ee 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -566,7 +566,7 @@ with ``Union[int, slice]`` and ``Union[T, Sequence]``. Advanced uses of self-types *************************** -Normally, mypy doesn't require annotation for first argument of instance and +Normally, mypy doesn't require annotations for the first arguments of instance and class methods. However, they may be needed to have more precise static typing for certain programming patterns. From c913a7fe97e4be544067b3aae6d19956cba28797 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:48:07 +0000 Subject: [PATCH 12/24] Update docs/source/more_types.rst Co-Authored-By: Michael Lee --- docs/source/more_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index e3a406ac47ee..18c887305396 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -573,7 +573,7 @@ for certain programming patterns. Restricted methods in generic classes ------------------------------------- -In generic classes some methods may be allowed to call only +In generic classes some methods may be allowed to be called only for certain values of type arguments: .. code-block:: python From 3cb098ca83b2352b8383c449ecb4cb100455ec5b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:48:16 +0000 Subject: [PATCH 13/24] Update docs/source/more_types.rst Co-Authored-By: Michael Lee --- docs/source/more_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 18c887305396..fdee80f1d76f 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -589,7 +589,7 @@ for certain values of type arguments: # "uppercase_item" with type "Callable[[Tag[str]], str]" ts.uppercase_item() # This is OK -This pattern also allows extraction of items in situations where type +This pattern also allows extraction of items in situations where the type argument is itself generic: .. code-block:: python From 52158e746a4096174eedb7e5024c518a30995e60 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 00:48:29 +0000 Subject: [PATCH 14/24] Update mypy/checker.py Co-Authored-By: Michael Lee --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1d1d7533c5e1..da3b79222f9d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -911,7 +911,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) note = None if isinstance(erased, Instance) and erased.type.is_protocol: msg = None - elif typ.arg_names[i] in ['self', 'cls']: + elif typ.arg_names[i] in {'self', 'cls'}: if (self.options.python_version[0] < 3 and is_same_type(erased, arg_type) and not isclass): msg = message_registry.INVALID_SELF_TYPE_OR_EXTRA_ARG From 0127c00560887ce63bac701e27f6d991e6c7682f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 01:24:02 +0000 Subject: [PATCH 15/24] Address CR --- docs/source/more_types.rst | 39 ++++++++++++++++++++---------- mypy/typeops.py | 8 +++--- test-data/unit/check-selftype.test | 11 +++++++++ 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index fdee80f1d76f..428ed3aca07c 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -579,6 +579,7 @@ for certain values of type arguments: .. code-block:: python T = TypeVar('T') + class Tag(Generic[T]): item: T def uppercase_item(self: C[str]) -> str: @@ -631,30 +632,42 @@ Mixin classes ------------- Using host class protocol as a self-type in mixin methods allows -static typing of mixin class patter: +more code re-usability for static typing of mixin classes. For example, +one can define a protocol that defines common functionality for +host classes instead of adding required abstract methods to every mixin: .. code-block:: python - class Resource(Protocol): - def close(self) -> int: ... + class Lockable(Protocol): + @property + def lock(self) -> Lock: ... - class AtomicClose: - def atomic_close(self: Resource) -> int: - with Lock(): - return self.close() + class AtomicCloseMixin: + def atomic_close(self: Lockable) -> int: + with self.lock: + ... - class File(AtomicClose): - def close(self) -> int: - ... + class AtomicOpenMixin: + def atomic_open(self: Lockable) -> int: + with self.lock: + ... - class Bad(AtomicClose): - ... + class File(AtomicCloseMixin, AtomicOpenMixin): + def __init__(self) -> None: + self.lock = Lock() - f: File + class Bad(AtomicCloseMixin): + pass + + f = File() b: Bad f.atomic_close() # OK b.atomic_close() # Error: Invalid self type for "atomic_close" +Note that the explicit self-type is *required* to be a protocol whenever it +is not a subtype of the current class. In this case mypy will check the validity +of the self-type only at the call site. + Precise typing of alternative constructors ------------------------------------------ diff --git a/mypy/typeops.py b/mypy/typeops.py index 428ab52d1408..06471630ca11 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -52,14 +52,14 @@ def type_object_type_from_function(signature: FunctionLike, # We need to first map B's __init__ to the type (List[T]) -> None. # We first record all non-trivial (explicit) self types. + default_self = fill_typevars(def_info) if not is_new and def_info == info and not info.is_newtype: - orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_kinds[0] == ARG_POS and - it.arg_types[0] != fill_typevars(def_info) else None) - for it in signature.items()] + orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self + and it.arg_kinds[0] == ARG_POS else None) for it in signature.items()] else: orig_self_types = [None] * len(signature.items()) - signature = bind_self(signature, original_type=fill_typevars(info), is_classmethod=is_new) + signature = bind_self(signature, original_type=default_self, is_classmethod=is_new) signature = cast(FunctionLike, map_type_from_supertype(signature, info, def_info)) special_sig = None # type: Optional[str] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index f85161498902..9dd4c43830d4 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -687,6 +687,17 @@ d: Lnk[str, float] = b >> c # OK e: Lnk[str, Tuple[int, float]] = a >> (b, c) # OK f: Lnk[str, Tuple[float, int]] = a >> (c, b) # E: Unsupported operand types for >> ("Lnk[str, Tuple[str, int]]" and "Tuple[Lnk[int, float], Lnk[str, int]]") +[case testSelfTypeMutuallyExclusiveRestrictions] +from typing import Generic, TypeVar + +T = TypeVar('T') + +class Foo(Generic[T]): + def f1(self: Foo[str]) -> None: + self.f2() # E: Invalid self argument "Foo[str]" to attribute function "f2" with type "Callable[[Foo[int]], None]" + def f2(self: Foo[int]) -> None: + self.f1() # E: Invalid self argument "Foo[int]" to attribute function "f1" with type "Callable[[Foo[str]], None]" + [case testSelfTypeStructureMetaclassMatch] from typing import TypeVar, Type, Generic, cast From 56f42bda81148a71bb08f87a718b3478b934802e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 01:40:28 +0000 Subject: [PATCH 16/24] Prettify one import --- mypy/infer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/infer.py b/mypy/infer.py index 57c85b70d837..c2f7fbd35e72 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -2,10 +2,11 @@ from typing import List, Optional, Sequence -from mypy.constraints import infer_constraints, infer_constraints_for_callable, SUBTYPE_OF +from mypy.constraints import ( + infer_constraints, infer_constraints_for_callable, SUBTYPE_OF, SUPERTYPE_OF +) from mypy.types import Type, TypeVarId, CallableType from mypy.solve import solve_constraints -from mypy.constraints import SUPERTYPE_OF def infer_function_type_arguments(callee_type: CallableType, From ea6376d816ab4cb87aec3bdfd6351f1886dcb841 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 01:50:04 +0000 Subject: [PATCH 17/24] Fix typo --- mypy/typeops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 06471630ca11..f9921a1e1614 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -52,7 +52,7 @@ def type_object_type_from_function(signature: FunctionLike, # We need to first map B's __init__ to the type (List[T]) -> None. # We first record all non-trivial (explicit) self types. - default_self = fill_typevars(def_info) + default_self = fill_typevars(info) if not is_new and def_info == info and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self and it.arg_kinds[0] == ARG_POS else None) for it in signature.items()] From eb3b2b7a329dcefed416a11bc236d59aea425ab7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 09:42:42 +0000 Subject: [PATCH 18/24] Add a comment --- mypy/checker.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 27ab005df2e6..c0acc970902d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -913,6 +913,9 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) isinstance(erased, TypeType) and isinstance(erased.item, Instance) and erased.item.type.is_protocol): + # We allow the explicit self-type to be not a supertype of + # the current class if it is a protocol. For such cases + # the consistency check will be performed at call sites. msg = None elif typ.arg_names[i] in {'self', 'cls'}: if (self.options.python_version[0] < 3 From 9a3454ef03937e9e83910b8394a60c92731a9c21 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 12:52:08 +0000 Subject: [PATCH 19/24] Simplify one test; use better example in docs --- docs/source/more_types.rst | 4 +++- test-data/unit/check-generics.test | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 428ed3aca07c..1752c588d148 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -677,11 +677,13 @@ classes are generic, self-type allows giving them precise signatures: .. code-block:: python T = TypeVar('T') - Q = TypeVar('Q', bound=Base[Any]) class Base(Generic[T]): + Q = TypeVar('Q', bound=Base[T]) + def __init__(self, item: T) -> None: self.item = item + @classmethod def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: return cls(item), cls(item) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index e648a4f12448..09a3616fb3a2 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1952,13 +1952,12 @@ reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__. from typing import Generic, TypeVar, Type, Tuple, Any T = TypeVar('T') -Q = TypeVar('Q', bound=Base[Any]) +Q = TypeVar('Q') class Base(Generic[T]): def __init__(self, item: T) -> None: ... @classmethod - def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: - return cls(item), cls(item) + def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: ... class Sub(Base[T]): ... From ab304e52d6b5d4ab81066d83d6ba81bfe9b083a7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 13:16:22 +0000 Subject: [PATCH 20/24] More doc tweaks --- docs/source/more_types.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 1752c588d148..4d561f3af9cb 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -590,7 +590,7 @@ for certain values of type arguments: # "uppercase_item" with type "Callable[[Tag[str]], str]" ts.uppercase_item() # This is OK -This pattern also allows extraction of items in situations where the type +This pattern also allows matching on nested types in situations where the type argument is itself generic: .. code-block:: python @@ -598,17 +598,17 @@ argument is itself generic: T = TypeVar('T') S = TypeVar('S') - class Node(Generic[T]): + class Storage(Generic[T]): def __init__(self, content: T) -> None: self.content = content - def first_item(self: Node[Sequence[S]]) -> S: + def first_chunk(self: Storage[Sequence[S]]) -> S: return self.content[0] - page: Node[List[str]] - page.get_first_item() # OK, type is "str" + page: Storage[List[str]] + page.first_chunk() # OK, type is "str" - Node(0).get_first_item() # Error: Invalid self argument "Node[int]" to attribute function - # "first_item" with type "Callable[[Node[Sequence[S]]], S]" + Storage(0).first_chunk() # Error: Invalid self argument "Storage[int]" to attribute function + # "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]" Finally, one can use overloads on self-type to express precise types of some tricky methods: @@ -645,12 +645,12 @@ host classes instead of adding required abstract methods to every mixin: class AtomicCloseMixin: def atomic_close(self: Lockable) -> int: with self.lock: - ... + # perform actions class AtomicOpenMixin: def atomic_open(self: Lockable) -> int: with self.lock: - ... + # perform actions class File(AtomicCloseMixin, AtomicOpenMixin): def __init__(self) -> None: @@ -665,7 +665,7 @@ host classes instead of adding required abstract methods to every mixin: b.atomic_close() # Error: Invalid self type for "atomic_close" Note that the explicit self-type is *required* to be a protocol whenever it -is not a subtype of the current class. In this case mypy will check the validity +is not a supertype of the current class. In this case mypy will check the validity of the self-type only at the call site. Precise typing of alternative constructors @@ -679,7 +679,7 @@ classes are generic, self-type allows giving them precise signatures: T = TypeVar('T') class Base(Generic[T]): - Q = TypeVar('Q', bound=Base[T]) + Q = TypeVar('Q', bound='Base[T]') def __init__(self, item: T) -> None: self.item = item From 64c672729f90508342bed8daa864938ae6703071 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 14:03:29 +0000 Subject: [PATCH 21/24] Some docstring, comments and cleanups --- mypy/checkmember.py | 15 ++++++++----- mypy/typeops.py | 55 ++++++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0bf7dff78a2a..0927149deeac 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -603,13 +603,18 @@ def check_self_arg(functype: FunctionLike, is_classmethod: bool, context: Context, name: str, msg: MessageBuilder) -> FunctionLike: - """For x.f where A.f: A1 -> T, check that meet(type(x), A) <: A1 for each overload. + """Check that an instance has a valid type for a method with annotated 'self'. - dispatched_arg_type is meet(B, A) in the following example - - def g(x: B): x.f + For example if the method is defined as: class A: - f: Callable[[A1], None] + def f(self: S) -> T: ... + then for 'x.f' we check that meet(type(x), A) <: S. If the method is overloaded, we + select only overloads items that satisfy this requirement. If there are no matching + overloads, an error is generated. + + Note: dispatched_arg_type uses a meet to select a relevant item in case if the + original type of 'x' is a union. This is done because several special methods + treat union types in ad-hoc manner, so we can't use MemberContext.self_type yet. """ items = functype.items() if not items: diff --git a/mypy/typeops.py b/mypy/typeops.py index f9921a1e1614..9ea5aef2b014 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -41,17 +41,9 @@ def type_object_type_from_function(signature: FunctionLike, def_info: TypeInfo, fallback: Instance, is_new: bool) -> FunctionLike: - # The __init__ method might come from a generic superclass - # (init_or_new.info) with type variables that do not map - # identically to the type variables of the class being constructed - # (info). For example - # - # class A(Generic[T]): def __init__(self, x: T) -> None: pass - # class B(A[List[T]], Generic[T]): pass - # - # We need to first map B's __init__ to the type (List[T]) -> None. - - # We first record all non-trivial (explicit) self types. + # We first need to record all non-trivial (explicit) self types in __init__, + # since they will not be available after we bind them. Note, we use explicit + # self-types only in the defining class, similar to __new__ (see below). default_self = fill_typevars(info) if not is_new and def_info == info and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self @@ -59,9 +51,19 @@ def type_object_type_from_function(signature: FunctionLike, else: orig_self_types = [None] * len(signature.items()) + # The __init__ method might come from a generic superclass 'def_info' + # with type variables that do not map identically to the type variables of + # the class 'info' being constructed. For example: + # + # class A(Generic[T]): + # def __init__(self, x: T) -> None: ... + # class B(A[List[T]]): + # ... + # + # We need to map B's __init__ to the type (List[T]) -> None. signature = bind_self(signature, original_type=default_self, is_classmethod=is_new) - signature = cast(FunctionLike, - map_type_from_supertype(signature, info, def_info)) + signature = cast(FunctionLike, map_type_from_supertype(signature, info, def_info)) + special_sig = None # type: Optional[str] if def_info.fullname() == 'builtins.dict': # Special signature! @@ -119,8 +121,8 @@ def map_type_from_supertype(typ: Type, For example, assume - . class D(Generic[S]) ... - . class C(D[E[T]], Generic[T]) ... + class D(Generic[S]): ... + class C(D[E[T]], Generic[T]): ... Now S in the context of D would be mapped to E[T] in the context of C. """ @@ -141,8 +143,14 @@ def map_type_from_supertype(typ: Type, return expand_type_by_instance(typ, inst_type) -def instance_or_var(typ: ProperType) -> bool: - # TODO: use more principled check for non-trivial self-types. +def supported_self_type(typ: ProperType) -> bool: + """Is this a supported kind of explicit self-types? + + Currently, this means a X or Type[X], where X is an instance or + a type variable with an instance upper bound. + """ + if isinstance(typ, TypeType): + return supported_self_type(typ.item) return (isinstance(typ, TypeVarType) or isinstance(typ, Instance) and typ != fill_typevars(typ.type)) @@ -181,7 +189,7 @@ class B(A): pass assert isinstance(method, CallableType) func = method if not func.arg_types: - # invalid method. return something + # Invalid method, return something. return cast(F, func) if func.arg_kinds[0] == ARG_STAR: # The signature is of the form 'def foo(*args, ...)'. @@ -191,12 +199,9 @@ class B(A): pass # TODO: infer bounds on the type of *args? return cast(F, func) self_param_type = get_proper_type(func.arg_types[0]) - if func.variables and (instance_or_var(self_param_type) or - (isinstance(self_param_type, TypeType) and - instance_or_var(self_param_type.item))): + if func.variables and supported_self_type(self_param_type): if original_type is None: - # Type check method override - # XXX value restriction as union? + # TODO: type check method override (see #7861). original_type = erase_to_bound(self_param_type) original_type = get_proper_type(original_type) @@ -206,7 +211,6 @@ class B(A): pass if (is_classmethod and isinstance(typearg, UninhabitedType) and isinstance(original_type, (Instance, TypeVarType, TupleType))): # In case we call a classmethod through an instance x, fallback to type(x) - # TODO: handle Union typearg = get_proper_type(infer_type_arguments(ids, self_param_type, TypeType(original_type), is_supertype=True)[0]) @@ -236,6 +240,7 @@ def expand(target: Type) -> Type: def erase_to_bound(t: Type) -> Type: + # TODO: use value restrictions to produce a union? t = get_proper_type(t) if isinstance(t, TypeVarType): return t.upper_bound @@ -289,8 +294,6 @@ def make_simplified_union(items: Sequence[Type], Note: This must NOT be used during semantic analysis, since TypeInfos may not be fully initialized. """ - # TODO: Make this a function living somewhere outside mypy.types. Most other non-trivial - # type operations are not static methods, so this is inconsistent. items = get_proper_types(items) while any(isinstance(typ, UnionType) for typ in items): all_items = [] # type: List[ProperType] From 5e5b1338d8367a852f75729ae0c78480cbd63b9d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 15:20:43 +0000 Subject: [PATCH 22/24] Two more tests for overloaded __init__ --- mypy/typeops.py | 3 +- test-data/unit/check-selftype.test | 56 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 9ea5aef2b014..82f00b705989 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -44,6 +44,7 @@ def type_object_type_from_function(signature: FunctionLike, # We first need to record all non-trivial (explicit) self types in __init__, # since they will not be available after we bind them. Note, we use explicit # self-types only in the defining class, similar to __new__ (see below). + # This is mostly useful for annotating library classes such as subprocess.Popen. default_self = fill_typevars(info) if not is_new and def_info == info and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self @@ -100,7 +101,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, and is_subtype(init_ret_type, default_ret_type, ignore_type_params=True) ): ret_type = init_ret_type # type: Type - elif orig_self_type is not None: + elif isinstance(orig_self_type, (Instance, TupleType)): ret_type = orig_self_type else: ret_type = default_ret_type diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 9dd4c43830d4..c4d1bb3d2e53 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -547,6 +547,7 @@ reveal_type(ci.from_item(conv)) # N: Revealed type is 'builtins.str' ci.from_item(bad) # E: Argument 1 to "from_item" of "C" has incompatible type "Callable[[str], str]"; expected "Callable[[int], str]" [case testSelfTypeRestrictedMethodOverloadInit] +from typing import TypeVar from lib import P, C reveal_type(P) # N: Revealed type is 'Overload(def [T] (use_str: Literal[True]) -> lib.P[builtins.str], def [T] (use_str: Literal[False]) -> lib.P[builtins.int])' @@ -557,6 +558,20 @@ reveal_type(C) # N: Revealed type is 'Overload(def [T] (item: T`1, use_tuple: L reveal_type(C(0, use_tuple=False)) # N: Revealed type is 'lib.C[builtins.int*]' reveal_type(C(0, use_tuple=True)) # N: Revealed type is 'lib.C[builtins.tuple[builtins.int*]]' +T = TypeVar('T') +class SubP(P[T]): + pass + +SubP('no') # E: No overload variant of "SubP" matches argument type "str" \ + # N: Possible overload variants: \ + # N: def [T] __init__(self, use_str: Literal[True]) -> SubP[T] \ + # N: def [T] __init__(self, use_str: Literal[False]) -> SubP[T] + +# This is a bit unfortunate: we don't have a way to map the overloaded __init__ to subtype. +x = SubP(use_str=True) # E: Need type annotation for 'x' +reveal_type(x) # N: Revealed type is '__main__.SubP[Any]' +y: SubP[str] = SubP(use_str=True) + [file lib.pyi] from typing import TypeVar, Generic, overload, Tuple from typing_extensions import Literal @@ -573,6 +588,47 @@ class C(Generic[T]): def __init__(self: C[T], item: T, use_tuple: Literal[False]) -> None: ... @overload def __init__(self: C[Tuple[T, ...]], item: T, use_tuple: Literal[True]) -> None: ... +[builtins fixtures/bool.pyi] + +[case testSelfTypeRestrictedMethodOverloadInitFallBacks] +from lib import PFallBack, PFallBackAny + +t: bool +xx = PFallBack(t) # E: Need type annotation for 'xx' +yy = PFallBackAny(t) # OK + +[file lib.pyi] +from typing import TypeVar, Generic, overload, Tuple, Any +from typing_extensions import Literal + +class PFallBack(Generic[T]): + @overload + def __init__(self: PFallBack[str], use_str: Literal[True]) -> None: ... + @overload + def __init__(self: PFallBack[int], use_str: Literal[False]) -> None: ... + @overload + def __init__(self, use_str: bool) -> None: ... + +class PFallBackAny(Generic[T]): + @overload + def __init__(self: PFallBackAny[str], use_str: Literal[True]) -> None: ... + @overload + def __init__(self: PFallBackAny[int], use_str: Literal[False]) -> None: ... + @overload + def __init__(self: PFallBackAny[Any], use_str: bool) -> None: ... +[builtins fixtures/bool.pyi] + +[case testSelfTypeRestrictedMethodOverloadInitBadTypeNoCrash] +from lib import P +P(0) +[file lib.pyi] +from typing import overload + +class P: + @overload + def __init__(self: Bad, x: int) -> None: ... # E: Name 'Bad' is not defined + @overload + def __init__(self) -> None: ... [case testSelfTypeNarrowBinding] from typing import TypeVar, List, Generic From ed5802454e755914d8b41ff820052c53056397ce Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 15:31:57 +0000 Subject: [PATCH 23/24] Fix self-check --- mypy/typeops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/typeops.py b/mypy/typeops.py index 82f00b705989..71dfd6e160d8 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -92,6 +92,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, from mypy.subtypes import is_subtype init_ret_type = get_proper_type(init_type.ret_type) + orig_self_type = get_proper_type(orig_self_type) default_ret_type = fill_typevars(info) if ( is_new From b33059e06f3c5046e4b4d773cef61b6fe7dd3e2c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 5 Nov 2019 13:04:50 +0000 Subject: [PATCH 24/24] Address CR --- docs/source/more_types.rst | 4 ++++ mypy/typeops.py | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 4d561f3af9cb..b5744fda3228 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -628,6 +628,10 @@ some tricky methods: return self.item return converter(self.item) +In particular, an :py:meth:`~object.__init__` method overloaded on self-type +may be useful to annotate generic class constructors where type arguments +depend on constructor parameters in a non-trivial way, see e.g. :py:class:`~subprocess.Popen`. + Mixin classes ------------- diff --git a/mypy/typeops.py b/mypy/typeops.py index 71dfd6e160d8..8db2158d809c 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -43,8 +43,9 @@ def type_object_type_from_function(signature: FunctionLike, is_new: bool) -> FunctionLike: # We first need to record all non-trivial (explicit) self types in __init__, # since they will not be available after we bind them. Note, we use explicit - # self-types only in the defining class, similar to __new__ (see below). - # This is mostly useful for annotating library classes such as subprocess.Popen. + # self-types only in the defining class, similar to __new__ (but not exactly the same, + # see comment in class_callable below). This is mostly useful for annotating library + # classes such as subprocess.Popen. default_self = fill_typevars(info) if not is_new and def_info == info and not info.is_newtype: orig_self_types = [(it.arg_types[0] if it.arg_types and it.arg_types[0] != default_self @@ -154,7 +155,7 @@ def supported_self_type(typ: ProperType) -> bool: if isinstance(typ, TypeType): return supported_self_type(typ.item) return (isinstance(typ, TypeVarType) or - isinstance(typ, Instance) and typ != fill_typevars(typ.type)) + (isinstance(typ, Instance) and typ != fill_typevars(typ.type))) F = TypeVar('F', bound=FunctionLike)