Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e4c2668

Browse files
authored
Call get_method_hook when methods are used in decorators (#10675)
When a method is used in a decorator, pass the full name to plugins through the get_method_hook function, just like we would normally do for other places that methods got called. Previously, in these situations, we would call get_function_hook instead with '{method_name} of {class_name}', which loses information about which module the class is located in, and gives information about a method to the hook meant for functions. Currently this is limited to situations where the method is accessed in the decorator itself, so @obj.meth def f() -> None: ... would work but method = obj.meth @method def f() -> None: ... probably wouldn't work.
1 parent 08e6ba8 commit e4c2668

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

mypy/checker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3596,10 +3596,17 @@ def visit_decorator(self, e: Decorator) -> None:
35963596
fullname = None
35973597
if isinstance(d, RefExpr):
35983598
fullname = d.fullname
3599+
# if this is a expression like @b.a where b is an object, get the type of b
3600+
# so we can pass it the method hook in the plugins
3601+
object_type = None # type: Optional[Type]
3602+
if fullname is None and isinstance(d, MemberExpr) and d.expr in self.type_map:
3603+
object_type = self.type_map[d.expr]
3604+
fullname = self.expr_checker.method_fullname(object_type, d.name)
35993605
self.check_for_untyped_decorator(e.func, dec, d)
36003606
sig, t2 = self.expr_checker.check_call(dec, [temp],
36013607
[nodes.ARG_POS], e,
3602-
callable_name=fullname)
3608+
callable_name=fullname,
3609+
object_type=object_type)
36033610
self.check_untyped_after_decorator(sig, e.func)
36043611
sig = set_callable_name(sig, e.func)
36053612
e.var.type = sig

test-data/unit/check-custom-plugin.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,23 @@ reveal_type(dynamic_signature(1)) # N: Revealed type is "builtins.int"
783783
[file mypy.ini]
784784
\[mypy]
785785
plugins=<ROOT>/test-data/unit/plugins/function_sig_hook.py
786+
787+
[case testPluginCalledCorrectlyWhenMethodInDecorator]
788+
# flags: --config-file tmp/mypy.ini
789+
from typing import TypeVar, Callable
790+
791+
T = TypeVar('T')
792+
class Foo:
793+
def a(self, x: Callable[[], T]) -> Callable[[], T]: ...
794+
795+
b = Foo()
796+
797+
@b.a
798+
def f() -> None:
799+
pass
800+
801+
reveal_type(f()) # N: Revealed type is "builtins.str"
802+
803+
[file mypy.ini]
804+
\[mypy]
805+
plugins=<ROOT>/test-data/unit/plugins/method_in_decorator.py
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from mypy.types import CallableType, Type
2+
from typing import Callable, Optional
3+
from mypy.plugin import MethodContext, Plugin
4+
5+
6+
class MethodDecoratorPlugin(Plugin):
7+
def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]:
8+
if 'Foo.a' in fullname:
9+
return method_decorator_callback
10+
return None
11+
12+
def method_decorator_callback(ctx: MethodContext) -> Type:
13+
if isinstance(ctx.default_return_type, CallableType):
14+
str_type = ctx.api.named_generic_type('builtins.str', [])
15+
return ctx.default_return_type.copy_modified(ret_type=str_type)
16+
return ctx.default_return_type
17+
18+
def plugin(version):
19+
return MethodDecoratorPlugin

0 commit comments

Comments
 (0)