diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c8dc69bdfbe4..4600e51db37a 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -4,7 +4,7 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef, - Overloaded, TypeVarType, UnionType, PartialType, + Overloaded, TypeVarType, UnionType, PartialType, UninhabitedType, DeletedType, NoneTyp, TypeType, function_type, get_type_vars, ) from mypy.nodes import ( @@ -306,7 +306,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, original_type) + signature = bind_self(functype, original_type, var.is_classmethod) if var.is_property: # A property cannot have an overloaded type => the cast # is fine. @@ -472,7 +472,7 @@ class B(A): pass tvars = [TypeVarDef(n, i + 1, [], 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) + t = bind_self(t, original_type, is_classmethod=True) return t.copy_modified(variables=tvars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(item, itype, is_classmethod, @@ -596,7 +596,7 @@ def map_type_from_supertype(typ: Type, sub_info: TypeInfo, F = TypeVar('F', bound=FunctionLike) -def bind_self(method: F, original_type: Type = None) -> F: +def bind_self(method: F, original_type: Type = None, is_classmethod: bool = False) -> F: """Return a copy of `method`, with the type of its first parameter (usually self or cls) bound to original_type. @@ -642,8 +642,13 @@ class B(A): pass # XXX value restriction as union? original_type = erase_to_bound(self_param_type) - typearg = infer_type_arguments([x.id for x in func.variables], - self_param_type, original_type)[0] + ids = [x.id for x in func.variables] + typearg = infer_type_arguments(ids, self_param_type, original_type)[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 = infer_type_arguments(ids, self_param_type, TypeType(original_type))[0] def expand(target: Type) -> Type: assert typearg is not None diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c82e619698c9..d0c4a56f2038 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -208,7 +208,6 @@ class B(A): [case testSelfTypeClone] from typing import TypeVar, Type - T = TypeVar('T', bound='C') class C: @@ -219,13 +218,26 @@ class C: 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() +class D(C): pass + +reveal_type(D.new) # E: Revealed type is 'def () -> __main__.D*' +reveal_type(D().new) # E: Revealed type is 'def () -> __main__.D*' +reveal_type(D.new()) # E: Revealed type is '__main__.D*' +reveal_type(D().new()) # E: Revealed type is '__main__.D*' +Q = TypeVar('Q', bound=C) + +def clone(arg: Q) -> Q: + reveal_type(arg.copy) # E: Revealed type is 'def () -> Q`-1' + reveal_type(arg.copy()) # E: Revealed type is 'Q`-1' + reveal_type(arg.new) # E: Revealed type is 'def () -> Q`-1' + reveal_type(arg.new()) # E: Revealed type is 'Q`-1' + return arg.copy() -def make(cls: Type[T]) -> T: - reveal_type(cls.new) # E: Revealed type is 'def () -> T`-1' +def make(cls: Type[Q]) -> Q: + reveal_type(cls.new) # E: Revealed type is 'def () -> Q`-1' + reveal_type(cls().new) # E: Revealed type is 'def () -> Q`-1' + reveal_type(cls().new()) # E: Revealed type is 'Q`-1' return cls.new() [builtins fixtures/classmethod.pyi]