Repository: python/mypy

Issue:
self type on property creates a spurious error
Running mypy master on this file:
```python
from typing import TypeVar, List
_T = TypeVar('_T')

class X:
    @property
    def __members__(self: _T) -> List[_T]: ...

reveal_type(X().__members__)
```
gives
```
../bin/selftypeproperty.py:8: error: Revealed type is 'builtins.list[selftypeproperty.X*]'
../bin/selftypeproperty.py:8: error: Invalid method type
```
The revealed type is correct but the "Invalid method type" doesn't make sense.


Discussion:
Is it the same if you rename `__members__` to `foo`?
Yes, doesn't make a difference.
I should look into it.

Note that adding a bound, i.e. `_T = TypeVar('_T', bound='X')` seems to fix this. 
Thanks! FYI, the code where I found this had a `Type` over a bound typevar (python/typeshed#1195). It has `_T = TypeVar('_T', bound=Enum)` and the property is `def __members__(self: Type[_T]) -> Mapping[str, _T]: ...`.

Resolving patch:
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -20,6 +20,8 @@
 from mypy.plugin import Plugin, AttributeContext
 from mypy import messages
 from mypy import subtypes
+from mypy import meet
+
 MYPY = False
 if MYPY:  # import for forward declaration only
     import mypy.checker
@@ -286,6 +288,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.
 
+    itype is the class object in which var is dedined
     original_type is the type of E in the expression E.var
     """
     # Found a member variable.
@@ -310,15 +313,21 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
                     msg.cant_assign_to_method(node)
 
             if not var.is_staticmethod:
-                # Class-level function objects and classmethods become bound
-                # methods: the former to the instance, the latter to the
-                # class.
+                # Class-level function objects and classmethods become bound methods:
+                # the former to the instance, the latter to the class.
                 functype = t
-                check_method_type(functype, itype, var.is_classmethod, node, msg)
+                # Use meet to narrow original_type to the dispatched type.
+                # For example, assume
+                # * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)
+                # * B.f: Callable[[B1], None] where B1 <: B (maybe B1 == B)
+                # * x: Union[A1, B1]
+                # 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(original_type, itype)
+                check_self_arg(functype, dispatched_type, var.is_classmethod, node, name, msg)
                 signature = bind_self(functype, original_type, var.is_classmethod)
                 if var.is_property:
-                    # A property cannot have an overloaded type => the cast
-                    # is fine.
+                    # A property cannot have an overloaded type => the cast is fine.
                     assert isinstance(signature, CallableType)
                     result = signature.ret_type
                 else:
@@ -370,33 +379,28 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str,
         return None
 
 
-def check_method_type(functype: FunctionLike, itype: Instance, is_classmethod: bool,
-                      context: Context, msg: MessageBuilder) -> None:
+def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool,
+                   context: Context, name: str, msg: MessageBuilder) -> None:
+    """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
+
+        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():
         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.invalid_method_type(item, context)
-        elif not is_classmethod:
-            # Check that self argument has type 'Any' or valid instance type.
-            selfarg = item.arg_types[0]
-            # If this is a method of a tuple class, correct for the fact that
-            # we passed to typ.fallback in analyze_member_access. See #1432.
-            if isinstance(selfarg, TupleType):
-                selfarg = selfarg.fallback
-            if not subtypes.is_subtype(selfarg, itype):
-                msg.invalid_method_type(item, context)
+            msg.no_formal_self(name, item, context)
         else:
-            # Check that cls argument has type 'Any' or valid class type.
-            # (This is sufficient for the current treatment of @classmethod,
-            # but probably needs to be revisited when we implement Type[C]
-            # or advanced variants of it like Type[<args>, C].)
-            clsarg = item.arg_types[0]
-            if isinstance(clsarg, CallableType) and clsarg.is_type_obj():
-                if not subtypes.is_equivalent(clsarg.ret_type, itype):
-                    msg.invalid_class_method_type(item, context)
-            else:
-                if not subtypes.is_equivalent(clsarg, AnyType(TypeOfAny.special_form)):
-                    msg.invalid_class_method_type(item, context)
+            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)):
+                msg.incompatible_self_argument(name, dispatched_arg_type, item,
+                                               is_classmethod, context)
 
 
 def analyze_class_attribute_access(itype: Instance,
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -863,11 +863,15 @@ def cannot_determine_type(self, name: str, context: Context) -> None:
     def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None:
         self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context)
 
-    def invalid_method_type(self, sig: CallableType, context: Context) -> None:
-        self.fail('Invalid method type', context)
-
-    def invalid_class_method_type(self, sig: CallableType, context: Context) -> None:
-        self.fail('Invalid class method type', context)
+    def no_formal_self(self, name: str, item: CallableType, context: Context) -> None:
+        self.fail('Attribute function "%s" with type %s does not accept self argument'
+                  % (name, self.format(item)), context)
+
+    def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType,
+                                   is_classmethod: bool, context: Context) -> None:
+        kind = 'class attribute function' if is_classmethod else 'attribute function'
+        self.fail('Invalid self argument %s to %s "%s" with type %s'
+                  % (self.format(arg), kind, name, self.format(sig)), context)
 
     def incompatible_conditional_function_def(self, defn: FuncDef) -> None:
         self.fail('All conditional function variants must have identical '


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -2105,7 +2105,7 @@ class B:
     a = A
     bad = lambda: 42
 
-B().bad() # E: Invalid method type
+B().bad() # E: Attribute function "bad" with type "Callable[[], int]" does not accept self argument
 reveal_type(B.a) # E: Revealed type is 'def () -> __main__.A'
 reveal_type(B().a) # E: Revealed type is 'def () -> __main__.A'
 reveal_type(B().a()) # E: Revealed type is '__main__.A'
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -507,8 +507,8 @@ class A:
     f = x # type: Callable[[], None]
     g = x # type: Callable[[B], None]
 a = None # type: A
-a.f() # E: Invalid method type
-a.g() # E: Invalid method type
+a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument
+a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]"
 
 [case testMethodWithDynamicallyTypedMethodAsDataAttribute]
 from typing import Any, Callable
@@ -568,7 +568,7 @@ class A(Generic[t]):
 ab = None # type: A[B]
 ac = None # type: A[C]
 ab.f()
-ac.f()   # E: Invalid method type
+ac.f()   # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]"
 
 [case testPartiallyTypedSelfInMethodDataAttribute]
 from typing import Any, TypeVar, Generic, Callable
diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test
--- a/test-data/unit/check-selftype.test
+++ b/test-data/unit/check-selftype.test
@@ -351,21 +351,130 @@ class E:
     def __init_subclass__(cls) -> None:
         reveal_type(cls)  # E: Revealed type is 'def () -> __main__.E'
 
-[case testSelfTypeProperty]
-from typing import TypeVar
+[case testSelfTypePropertyUnion]
+from typing import Union
+class A:
+    @property
+    def f(self: A) -> int: pass
 
-T = TypeVar('T', bound='A')
+class B:
+    @property
+    def f(self: B) -> int: pass
+x: Union[A, B]
+reveal_type(x.f)  # E: Revealed type is 'builtins.int'
 
-class A:
+[builtins fixtures/property.pyi]
+
+[case testSelfTypeProperSupertypeAttribute]
+from typing import Callable, TypeVar
+class K: pass
+T = TypeVar('T', bound=K)
+class A(K):
     @property
-    def member(self: T) -> T:
-        pass
+    def g(self: K) -> int: return 0
+    @property
+    def gt(self: T) -> T: return self
+    f: Callable[[object], int]
+    ft: Callable[[T], T]
+
+class B(A):
+    pass
+
+reveal_type(A().g)  # E: Revealed type is 'builtins.int'
+reveal_type(A().gt)  # E: Revealed type is '__main__.A*'
+reveal_type(A().f())  # E: Revealed type is 'builtins.int'
+reveal_type(A().ft())  # E: Revealed type is '__main__.A*'
+reveal_type(B().g)  # E: Revealed type is 'builtins.int'
+reveal_type(B().gt)  # E: Revealed type is '__main__.B*'
+reveal_type(B().f())  # E: Revealed type is 'builtins.int'
+reveal_type(B().ft())  # E: Revealed type is '__main__.B*'
+
+[builtins fixtures/property.pyi]
+
+[case testSelfTypeProperSupertypeAttributeTuple]
+from typing import Callable, TypeVar, Tuple
+T = TypeVar('T')
+class A(Tuple[int, int]):
+    @property
+    def g(self: object) -> int: return 0
+    @property
+    def gt(self: T) -> T: return self
+    f: Callable[[object], int]
+    ft: Callable[[T], T]
 
 class B(A):
     pass
 
-reveal_type(A().member)  # E: Revealed type is '__main__.A*'
-reveal_type(B().member)  # E: Revealed type is '__main__.B*'
+reveal_type(A().g)  # E: Revealed type is 'builtins.int'
+reveal_type(A().gt)  # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.A]'
+reveal_type(A().f())  # E: Revealed type is 'builtins.int'
+reveal_type(A().ft())  # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.A]'
+reveal_type(B().g)  # E: Revealed type is 'builtins.int'
+reveal_type(B().gt)  # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.B]'
+reveal_type(B().f())  # E: Revealed type is 'builtins.int'
+reveal_type(B().ft())  # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.B]'
+
+[builtins fixtures/property.pyi]
+
+[case testSelfTypeProperSupertypeAttributeMeta]
+from typing import Callable, TypeVar, Type
+T = TypeVar('T')
+class A(type):
+    @property
+    def g(cls: object) -> int: return 0
+    @property
+    def gt(cls: T) -> T: return cls
+    f: Callable[[object], int]
+    ft: Callable[[T], T]
+
+class B(A):
+    pass
+
+class X(metaclass=B):
+    def __init__(self, x: int) -> None: pass
+class Y(X): pass
+X1: Type[X]
+reveal_type(X.g)  # E: Revealed type is 'builtins.int'
+reveal_type(X.gt)  # E: Revealed type is 'def (x: builtins.int) -> __main__.X'
+reveal_type(X.f())  # E: Revealed type is 'builtins.int'
+reveal_type(X.ft())  # E: Revealed type is 'def (x: builtins.int) -> __main__.X'
+reveal_type(Y.g)  # E: Revealed type is 'builtins.int'
+reveal_type(Y.gt)  # E: Revealed type is 'def (x: builtins.int) -> __main__.Y'
+reveal_type(Y.f())  # E: Revealed type is 'builtins.int'
+reveal_type(Y.ft())  # E: Revealed type is 'def (x: builtins.int) -> __main__.Y'
+reveal_type(X1.g)  # E: Revealed type is 'builtins.int'
+reveal_type(X1.gt)  # E: Revealed type is 'Type[__main__.X]'
+reveal_type(X1.f())  # E: Revealed type is 'builtins.int'
+reveal_type(X1.ft())  # E: Revealed type is 'Type[__main__.X]'
+
+[builtins fixtures/property.pyi]
+
+[case testSelfTypeProperSupertypeAttributeGeneric]
+from typing import Callable, TypeVar, Generic
+Q = TypeVar('Q', covariant=True)
+class K(Generic[Q]):
+    q: Q
+T = TypeVar('T')
+class A(K[Q]):
+    @property
+    def g(self: K[object]) -> int: return 0
+    @property
+    def gt(self: K[T]) -> T: return self.q
+    f: Callable[[object], int]
+    ft: Callable[[T], T]
+
+class B(A[Q]):
+    pass
+a: A[int]
+b: B[str]
+reveal_type(a.g)  # E: Revealed type is 'builtins.int'
+--reveal_type(a.gt)  # E: Revealed type is 'builtins.int'
+reveal_type(a.f())  # E: Revealed type is 'builtins.int'
+reveal_type(a.ft())  # E: Revealed type is '__main__.A*[builtins.int]'
+reveal_type(b.g)  # E: Revealed type is 'builtins.int'
+--reveal_type(b.gt)  # E: Revealed type is '__main__.B*[builtins.str]'
+reveal_type(b.f())  # E: Revealed type is 'builtins.int'
+reveal_type(b.ft())  # E: Revealed type is '__main__.B*[builtins.str]'
 
 [builtins fixtures/property.pyi]
 
@@ -376,3 +485,21 @@ class A:
     # def g(self: None) -> None: ... see in check-python2.test
 [out]
 main:3: error: Self argument missing for a non-static method (or an invalid type for self)
+
+[case testUnionPropertyField]
+from typing import Union
+
+class A:
+    x: int
+
+class B:
+    @property
+    def x(self) -> int: return 1
+
+class C:
+    @property
+    def x(self) -> int: return 1
+
+ab: Union[A, B, C]
+reveal_type(ab.x)  # E: Revealed type is 'builtins.int'
+[builtins fixtures/property.pyi]