diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9c2bf86762726..3359965a28f08 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -707,8 +707,12 @@ def check_call(self, False, False, False, self.msg, original_type=callee, chk=self.chk, in_literal_context=self.is_literal_context()) + callable_name = callee.type.fullname() + ".__call__" + # Apply method signature hook, if one exists + call_function = self.transform_callee_type( + callable_name, call_function, args, arg_kinds, context, arg_names, callee) return self.check_call(call_function, args, arg_kinds, context, arg_names, - callable_node, arg_messages) + callable_node, arg_messages, callable_name, callee) elif isinstance(callee, TypeVarType): return self.check_call(callee.upper_bound, args, arg_kinds, context, arg_names, callable_node, arg_messages) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 5505d487ab7e5..20d0b5e59f393 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -394,11 +394,13 @@ class Foo: def __setitem__(self, index: str, value: str) -> None: ... def __iter__(self) -> Iterator[str]: ... def __next__(self) -> str: ... + def __call__(self, *args: str) -> str: ... def m(self, arg: str) -> str: ... foo = Foo() reveal_type(foo.m(2)) # E: Revealed type is 'builtins.int' reveal_type(foo[3]) # E: Revealed type is 'builtins.int' +reveal_type(foo(4, 5, 6)) # E: Revealed type is 'builtins.int' foo[4] = 5 for x in foo: reveal_type(x) # E: Revealed type is 'builtins.int*' @@ -536,3 +538,18 @@ func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] [file mypy.ini] [[mypy] plugins=/test-data/unit/plugins/arg_kinds.py + +[case testHookCallableInstance] +# flags: --config-file tmp/mypy.ini +from typing import Generic, TypeVar +T = TypeVar("T") +class Class(Generic[T]): + def __init__(self, one: T): ... + def __call__(self, two: T) -> int: ... +reveal_type(Class("hi")("there")) # E: Revealed type is 'builtins.str*' +instance = Class(3.14) +reveal_type(instance(2)) # E: Revealed type is 'builtins.float*' + +[file mypy.ini] +[[mypy] +plugins=/test-data/unit/plugins/callable_instance.py diff --git a/test-data/unit/plugins/callable_instance.py b/test-data/unit/plugins/callable_instance.py new file mode 100644 index 0000000000000..40e7df4185392 --- /dev/null +++ b/test-data/unit/plugins/callable_instance.py @@ -0,0 +1,23 @@ +from mypy.plugin import MethodContext, Plugin +from mypy.types import Instance, Type + +class CallableInstancePlugin(Plugin): + def get_function_hook(self, fullname): + assert not fullname.endswith(' of Foo') + + def get_method_hook(self, fullname): + # Ensure that all names are fully qualified + assert not fullname.endswith(' of Foo') + + if fullname == '__main__.Class.__call__': + return my_hook + + return None + +def my_hook(ctx: MethodContext) -> Type: + if isinstance(ctx.type, Instance) and len(ctx.type.args) == 1: + return ctx.type.args[0] + return ctx.default_return_type + +def plugin(version): + return CallableInstancePlugin