Repository: python/mypy (Python)

Issue:
Super in a generic classmethod gives error
* Are you reporting a bug, or opening a feature request? **Bug**
* Please insert below the code you are checking with mypy,

```
from typing import TypeVar, Type

T = TypeVar("T", bound="A")

class A:
    @classmethod
    def foo(cls: Type[T]) -> T:
        print(cls.__qualname__)
        return cls()

class B(A):
    @classmethod
    def foo(cls: Type[T]) -> T:
        return super().foo()

B.foo()
```

* What is the actual behavior/output? 
```
scratch.py:14: error: Returning Any from function declared to return "T"
scratch.py:14: error: Argument 2 for "super" not an instance of argument 1
```

* What are the versions of mypy and Python you are using?
  **Python 3.8.5
  mypy 0.790+dev.5e9682143720263466f0aaed1924ccb218160ee0**
  Do you see the same issue after installing mypy from Git master? **Yes**
* What are the mypy flags you are using? **--strict**


Hints:
I've encountered this issue also. 

In my case, changing my equivalent of `B` provided a workaround. It looks like this:
```python
class B(A):
    @classmethod
    def foo(cls) -> "B":
        return super().foo()
```

This gets rid of the warnings I was getting, but I suspect it won't play nice with any further subclasses. I do want to be able to subclass `B` further, so is there a better way of working around this without resorting to `#type: ignore`?
I think this is still an issue in mypy v0.910

```python
T = TypeVar("T", bound="ConfigFile")  # pylint: disable=invalid-name


class ConfigFile:
    def __init__(self, content: str) -> None:
        self._content: str = content

    @classmethod
    def from_file(cls: Type[T], file_path: str) -> T:
        with open(file=file_path, mode="r") as _file_handler:
            return cls(content=_file_handler.read())

class FooConfigFile(ConfigFile):
    @classmethod
    def from_file(cls: Type[T], file_path="/tmp/foo") -> T:
        return super().from_file(file_path=file_path)
```

results in 

`error: Argument 2 for "super" not an instance of argument 1`

![Selection_20210811_04](https://user-images.githubusercontent.com/17593457/129025667-a61cb318-9c3f-4ae1-b508-bac636e4874f.png)

Confirming that this still happens in Mypy 0.930: https://mypy-play.net/?mypy=latest&python=3.10&gist=94cf4781e3ebc86b2d4ea9ef4cf7f2e7
0.942 still has this issue
```
    @classmethod
    def _extract(cls: Type[RegexExtractorType], match: Match) -> Dict[str, Any]:
        kwargs = super()._extract(match)
```

`error: Argument 2 for "super" not an instance of argument 1`
A [workaround](https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=7c42454491949a6673daa75bc9c310bc) is just to use a second `TypeVar` for the subclass:

```python
from typing import TypeVar, Type

T = TypeVar("T", bound="A")

class A:
    @classmethod
    def foo(cls: Type[T]) -> T:
        print(cls.__qualname__)
        return cls()

T2 = TypeVar("T2", bound="B")

class B(A):
    @classmethod
    def foo(cls: Type[T2]) -> T2:
        return super().foo()

B.foo()
```
@AlexWaygood, that's cool, but it doesn't work for `__new__`/metaclasses:

``` python
# test_case.py
from typing import Any, Mapping, Tuple, Type, TypeVar

_TT = TypeVar("_TT", bound=type)


class Foo(type):
    def __new__(
        mcls: Type[_TT],
        name: str,
        bases: Tuple[Type, ...],
        namespace: Mapping[str, Any],
        **kw: Any,
    ) -> _TT:
        return super().__new__(mcls, name, bases, namespace, **kw)  # <-- still errors
```

``` sh
% mypy --version
mypy 0.941
% python --version
Python 3.9.10
% mypy --config=/dev/null test_case.py
/dev/null: No [mypy] section in config file
test_case.py:15: error: Argument 2 for "super" not an instance of argument 1
Found 1 error in 1 file (checked 1 source file)
```
@posita, there's two issues in your example:

1. The [stub for `type` in typeshed](https://github.com/python/typeshed/blob/e8fe316a74b49fae43efe36859c294888160d399/stdlib/builtins.pyi#L176) says that the `namespace` argument in `type.__new__` has to be `dict[str, Any]`. But the third  parameter of `Foo.__new__` is annotated with `Mapping[str, Any]`. `Mapping[str, Any]` is not a subtype of `dict[str, Any]`, so passing an object of type `Mapping[str, Any]` to the third argument of `super().__new__()` is unsafe.
 
2. Your `_TT` TypeVar needs to be bound to a forward reference `"Foo"` rather than bound to `type`.
 
If I modify your snippet to the following:

```python
from typing import Any, Dict, Mapping, Tuple, Type, TypeVar

_TT = TypeVar("_TT", bound="Foo")


class Foo(type):
    def __new__(
        mcls: Type[_TT],
        name: str,
        bases: Tuple[type, ...],
        namespace: Dict[str, Any],
        **kw: Any,
    ) -> _TT:
        return super().__new__(mcls, name, bases, namespace, **kw)
```

Then mypy [is happy with it](https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=f05711607ae570b0f6454c89c86ddff0) 🙂

(I'm not saying this is ideal — it's just a workaround.)
The stub for typeshed is correct that the `namespace` argument to `type.__new__` has to be a `dict` rather than any old `Mapping`, FWIW:

```python

>>> from collections import UserDict
>>> type.__new__(type, 'Foo', (), UserDict())
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: type.__new__() argument 3 must be dict, not UserDict
>>> type.__new__(type, 'Foo', (), {})
<class '__main__.Foo'>
```
Ah! Brilliant! My bad. Thanks for the schoolin'! 🙇
It's more of an inconvenience than a bug. Technically mypy is correct, `Type[T]` bound to a higher class in the hierarchy is not a subclass of the current class. Defining a new type for every subclass though is quite annoying, so there should be some sort of a solution.

Patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -153,6 +153,7 @@
     is_generic_instance,
     is_named_instance,
     is_optional,
+    is_self_type_like,
     remove_optional,
 )
 from mypy.typestate import TypeState
@@ -4268,9 +4269,22 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
 
         # The base is the first MRO entry *after* type_info that has a member
         # with the right name
-        try:
+        index = None
+        if type_info in mro:
             index = mro.index(type_info)
-        except ValueError:
+        else:
+            method = self.chk.scope.top_function()
+            assert method is not None
+            # Mypy explicitly allows supertype upper bounds (and no upper bound at all)
+            # for annotating self-types. However, if such an annotation is used for
+            # checking super() we will still get an error. So to be consistent, we also
+            # allow such imprecise annotations for use with super(), where we fall back
+            # to the current class MRO instead.
+            if is_self_type_like(instance_type, is_classmethod=method.is_class):
+                if e.info and type_info in e.info.mro:
+                    mro = e.info.mro
+                    index = mro.index(type_info)
+        if index is None:
             self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
             return AnyType(TypeOfAny.from_error)
 
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -3313,6 +3313,16 @@ def is_literal_type(typ: ProperType, fallback_fullname: str, value: LiteralValue
     return typ.value == value
 
 
+def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool:
+    """Does this look like a self-type annotation?"""
+    typ = get_proper_type(typ)
+    if not is_classmethod:
+        return isinstance(typ, TypeVarType)
+    if not isinstance(typ, TypeType):
+        return False
+    return isinstance(typ.item, TypeVarType)
+
+
 names: Final = globals().copy()
 names.pop("NOT_READY", None)
 deserialize_map: Final = {


Test patch:
diff --git a/test-data/unit/check-super.test b/test-data/unit/check-super.test
--- a/test-data/unit/check-super.test
+++ b/test-data/unit/check-super.test
@@ -380,3 +380,32 @@ class A:
 class B(A):
     def h(self, t: Type[None]) -> None:
         super(t, self).f # E: Unsupported argument 1 for "super"
+
+[case testSuperSelfTypeInstanceMethod]
+from typing import TypeVar, Type
+
+T = TypeVar("T", bound="A")
+
+class A:
+    def foo(self: T) -> T: ...
+
+class B(A):
+    def foo(self: T) -> T:
+        reveal_type(super().foo())  # N: Revealed type is "T`-1"
+        return super().foo()
+
+[case testSuperSelfTypeClassMethod]
+from typing import TypeVar, Type
+
+T = TypeVar("T", bound="A")
+
+class A:
+    @classmethod
+    def foo(cls: Type[T]) -> T: ...
+
+class B(A):
+    @classmethod
+    def foo(cls: Type[T]) -> T:
+        reveal_type(super().foo())  # N: Revealed type is "T`-1"
+        return super().foo()
+[builtins fixtures/classmethod.pyi]