Repository: python/mypy

Issue:
Confusing message for invalid self type annotation
The error message for an accidental type annotation for the self argument is confusing.  Example:

```py
...
class A:
    def f(self):
        # type: (None) -> None
        ...
```

This gives an error like `The erased type of self 'void' is not a supertype of its class 'A'`. 

Usually a message like this would be better:

```
x.py:5:error: Invalid type for self, or extra argument type in function annotation
x.py:5:note: (Hint: typically annotations omit the type for self)
```

In Python 3, the related problem is typically a missing self:

```py
class A:
    def f(x: int) -> None: ...
```

Here a better error message would be something like this:

```
x.py:5:error: Self argument missing (or an invalid type for self)
```

Perhaps we can revert to the current message (or display it as a note) if it looks like the user was actually trying to use a self type, based on some heuristics. For example, `self: X` looks like a self type annotation since the argument is named `self`, and using a type variable `T` as the type of the first argument is potentially an attempt at a proper self type.

This was reported by a user, but I've seen it myself as well -- typically when I forget to add the self argument.


Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -572,13 +572,28 @@ def is_implicit_any(t: Type) -> bool:
                     if (isinstance(defn, FuncDef) and ref_type is not None and i == 0
                             and not defn.is_static
                             and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
-                        if defn.is_class or defn.name() in ('__new__', '__init_subclass__'):
+                        isclass = defn.is_class or defn.name() in ('__new__', '__init_subclass__')
+                        if isclass:
                             ref_type = mypy.types.TypeType(ref_type)
                         erased = erase_to_bound(arg_type)
                         if not is_subtype_ignoring_tvars(ref_type, erased):
-                            self.fail("The erased type of self '{}' "
-                                      "is not a supertype of its class '{}'"
-                                      .format(erased, ref_type), defn)
+                            note = None
+                            if 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 = ("Invalid type for self, or extra argument type "
+                                           "in function annotation")
+                                    note = '(Hint: typically annotations omit the type for self)'
+                                else:
+                                    msg = ("The erased type of self '{}' "
+                                           "is not a supertype of its class '{}'"
+                                           ).format(erased, ref_type)
+                            else:
+                                msg = ("Self argument missing for a non-static method "
+                                       "(or an invalid type for self)")
+                            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
@@ -2322,6 +2337,10 @@ def warn(self, msg: str, context: Context) -> None:
         """Produce a warning message."""
         self.msg.warn(msg, context)
 
+    def note(self, msg: str, context: Context) -> None:
+        """Produce a note."""
+        self.msg.note(msg, context)
+
     def iterable_item_type(self, instance: Instance) -> Type:
         iterable = map_instance_to_supertype(
             instance,


Test patch:
diff --git a/test-data/unit/check-python2.test b/test-data/unit/check-python2.test
--- a/test-data/unit/check-python2.test
+++ b/test-data/unit/check-python2.test
@@ -299,3 +299,12 @@ M = None  # type: Any
 class MM(M): pass
 class A(object):
     __metaclass__ = MM
+
+[case testSelfTypeNotSelfType2]
+class A:
+    def g(self):
+        # type: (None) -> None
+        pass
+[out]
+main:2: error: Invalid type for self, or extra argument type in function annotation
+main:2: note: (Hint: typically annotations omit the type for self)
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
@@ -271,7 +271,7 @@ class C:
         return cls()
 
 class D:
-    def foo(self: str) -> str:  # E: The erased type of self 'builtins.str' is not a supertype of its class '__main__.D'
+    def foo(self: Q) -> Q:  # E: The erased type of self '__main__.B' is not a supertype of its class '__main__.D'
         return self
 
     @staticmethod
@@ -279,7 +279,7 @@ class D:
         return self
 
     @classmethod
-    def cfoo(cls: Type[str]) -> str:  # E: The erased type of self 'Type[builtins.str]' is not a supertype of its class 'Type[__main__.D]'
+    def cfoo(cls: Type[Q]) -> Q:  # E: The erased type of self 'Type[__main__.B]' is not a supertype of its class 'Type[__main__.D]'
         return cls()
 
 [builtins fixtures/classmethod.pyi]
@@ -356,3 +356,11 @@ reveal_type(A().member)  # E: Revealed type is '__main__.A*'
 reveal_type(B().member)  # E: Revealed type is '__main__.B*'
 
 [builtins fixtures/property.pyi]
+
+[case testSelfTypeNotSelfType]
+# Friendlier error messages for common mistakes. See #2950
+class A:
+    def f(x: int) -> None: ...
+    # 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)