Skip to content

Fix incorrect name lookup for decorated methods #8175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 20, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3936,11 +3936,34 @@ 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 isinstance(node, OverloadedFuncDef) and isinstance(self.statement, FuncDef):
return self.is_overloaded_item(node, self.statement) and line_diff > 0
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, overload: OverloadedFuncDef, func: FuncDef) -> bool:
"""Check whehter the function belongs to the overloaded variants"""
return func in overload.items

def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
if fullname is None:
return False
Expand Down
41 changes: 41 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -6596,3 +6596,44 @@ 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'

[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]:
return A

[builtins fixtures/classmethod.pyi]
1 change: 1 addition & 0 deletions test-data/unit/fixtures/staticmethod.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class type:
class function: pass

staticmethod = object() # Dummy definition.
property = object() # Dummy definition

class int:
@staticmethod
Expand Down