diff --git a/mypy/semanal.py b/mypy/semanal.py index 488deb80e21b..0bf18a7b2197 100644 --- 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 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 333ffd7b7ccd..be765be67bfe 100644 --- 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] diff --git a/test-data/unit/fixtures/staticmethod.pyi b/test-data/unit/fixtures/staticmethod.pyi index 14254e64dcb1..7d5d98634e48 100644 --- a/test-data/unit/fixtures/staticmethod.pyi +++ b/test-data/unit/fixtures/staticmethod.pyi @@ -9,6 +9,7 @@ class type: class function: pass staticmethod = object() # Dummy definition. +property = object() # Dummy definition class int: @staticmethod