Repository: python/mypy

Issue:
mypy incorrectly resolves names inside decorated methods
* Are you reporting a bug, or opening a feature request?

A bug

* Please insert below the code you are checking with mypy,
  or a mock-up repro if the source is private. We would appreciate
  if you try to simplify your case to a minimal repro.
```
from typing import Type

class A:
    pass

class B:
    @staticmethod
    def A() -> Type[A]:
        return A

assert A is B.A()
```

* What is the actual behavior/output?
```
$ mypy mypy_fail.py
mypy_fail.py:8: error: Function "mypy_fail.B.A" is not valid as a type
mypy_fail.py:8: note: Perhaps you need "Callable[...]" or a callback protocol?
Found 1 error in 1 file (checked 1 source file)
```

* What is the behavior/output you expect?

I expect this to be accepted - it works fine in python

Probably of interest: regular methods work like I expect. `classmethod` and `property` complain the same way, so I assume this has to do with decorators. For functions, I can't reproduce syntax like this that's also valid python (I get `UnboundLocalError`s), so that's as far as I got with debugging.

* What are the versions of mypy and Python you are using?
  Do you see the same issue after installing mypy from Git master?

mypy `0.750` and python 3.6/3.8. I also get it on git master.

* What are the mypy flags you are using? (For example --strict-optional)

No flags required



Discussion:
This is indeed a bug. You can use a type alias as workaround:
```python
_A = A
class B:
    @staticmethod
    def A() -> Type[_A]:
        return A
```
There's another possible workaround:
```python
from typing import Type

class C:
    pass

class B:
    @staticmethod
    def A() -> Type[C]:
        return C
```
which I confirm that at line 8, `lookup_qualified` will correctly lookup the node with fullname of `__main__.C`. However, with the failed code, the lookup result will be `__main__.B.A`, which caused the error. So I think we need to modify `lookup_qualified` to fix this. @ilevkivskyi 

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -3936,11 +3936,41 @@ class C:
         #       caught.
         assert self.statement  # we are at class scope
         return (node is None
-                or node.line < self.statement.line
+                or self.is_textually_before_statement(node)
                 or not self.is_defined_in_current_module(node.fullname)
                 or isinstance(node, TypeInfo)
                 or (isinstance(node, PlaceholderNode) and node.becomes_typeinfo))
 
+    def is_textually_before_statement(self, node: SymbolNode) -> bool:
+        """Check if a node is defined textually before the current statement
+
+        Note that decorated functions' line number are the same as
+        the top decorator.
+        """
+        assert self.statement
+        line_diff = self.statement.line - node.line
+
+        # The first branch handles reference an overloaded function variant inside itself,
+        # this is a corner case where mypy technically deviates from runtime name resolution,
+        # but it is fine because we want an overloaded function to be treated as a single unit.
+        if self.is_overloaded_item(node, self.statement):
+            return False
+        elif isinstance(node, Decorator) and not node.is_overload:
+            return line_diff > len(node.original_decorators)
+        else:
+            return line_diff > 0
+
+    def is_overloaded_item(self, node: SymbolNode, statement: Statement) -> bool:
+        """Check whehter the function belongs to the overloaded variants"""
+        if isinstance(node, OverloadedFuncDef) and isinstance(statement, FuncDef):
+            in_items = statement in {item.func if isinstance(item, Decorator)
+                                     else item for item in node.items}
+            in_impl = (node.impl is not None and
+                      ((isinstance(node.impl, Decorator) and statement is node.impl.func)
+                       or statement is node.impl))
+            return in_items or in_impl
+        return False
+
     def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
         if fullname is None:
             return False


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
@@ -6597,3 +6597,45 @@ reveal_type(D() + "str")  # N: Revealed type is 'Any'
 reveal_type(0.5 + D1())  # N: Revealed type is 'Any'
 reveal_type(D1() + 0.5)  # N: Revealed type is '__main__.D1'
 [builtins fixtures/primitives.pyi]
+
+[case testRefMethodWithDecorator]
+from typing import Type
+
+class A:
+    pass
+
+class B:
+    @staticmethod
+    def A() -> Type[A]: ...
+    @staticmethod
+    def B() -> Type[A]:  # E: Function "__main__.B.A" is not valid as a type \
+                         # N: Perhaps you need "Callable[...]" or a callback protocol?
+        return A
+
+class C:
+    @property
+    @staticmethod
+    def A() -> Type[A]:
+        return A
+
+[builtins fixtures/staticmethod.pyi]
+
+[case testRefMethodWithOverloadDecorator]
+from typing import Type, overload
+
+class A:
+    pass
+
+class B:
+    @classmethod
+    @overload
+    def A(cls, x: int) -> Type[A]: ...
+    @classmethod
+    @overload
+    def A(cls, x: str) -> Type[A]: ...
+    @classmethod
+    def A(cls, x: object) -> Type[A]: ...
+    def B(cls, x: int) -> Type[A]: ...  # E: Function "__main__.B.A" is not valid as a type \
+                                        # N: Perhaps you need "Callable[...]" or a callback protocol?
+
+[builtins fixtures/classmethod.pyi]