From fa0424cfd682cda10ece5ce4c180d96c1bcdac6c Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 29 Jul 2019 00:12:58 +0200 Subject: [PATCH] Fix signatures for wraps, see #1058 --- jedi/evaluate/signature.py | 12 ++++----- jedi/plugins/stdlib.py | 40 +++++++++++++++++++++++++++- test/test_evaluate/test_signature.py | 35 ++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/jedi/evaluate/signature.py b/jedi/evaluate/signature.py index 20aa4c65..1b4d60dc 100644 --- a/jedi/evaluate/signature.py +++ b/jedi/evaluate/signature.py @@ -30,12 +30,6 @@ class _SignatureMixin(object): s += ' -> ' + annotation return s - def get_param_names(self, resolve_stars=True): - param_names = self._function_context.get_param_names() - if self.is_bound: - return param_names[1:] - return param_names - class AbstractSignature(_SignatureMixin): def __init__(self, context, is_bound=False): @@ -50,6 +44,12 @@ class AbstractSignature(_SignatureMixin): def annotation_string(self): return '' + def get_param_names(self, resolve_stars=True): + param_names = self._function_context.get_param_names() + if self.is_bound: + return param_names[1:] + return param_names + def bind(self, context): raise NotImplementedError diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index fc138cd2..0d25c394 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -642,6 +642,43 @@ class ItemGetterCallable(ContextWrapper): return context_set +@argument_clinic('func, /') +def _functools_wraps(funcs): + return ContextSet(WrapsCallable(func) for func in funcs) + + +class WrapsCallable(ContextWrapper): + @repack_with_argument_clinic('func, /') + def py__call__(self, funcs): + return ContextSet({Wrapped(func, self._wrapped_context) for func in funcs}) + + +class Wrapped(ContextWrapper): + def __init__(self, func, original_function): + super(Wrapped, self).__init__(func) + self._original_function = original_function + + @property + def name(self): + return self._original_function.name + + def get_signatures(self): + return [ + ReplacedNameSignature(sig, self._original_function.name) + for sig in self._wrapped_context.get_signatures() + ] + + +class ReplacedNameSignature(SignatureWrapper): + def __init__(self, signature, name): + super(ReplacedNameSignature, self).__init__(signature) + self._name = name + + @property + def name(self): + return self._name + + @argument_clinic('*args, /', want_obj=True, want_arguments=True) def _operator_itemgetter(args_context_set, obj, arguments): return ContextSet([ @@ -675,7 +712,8 @@ _implemented = { }, 'functools': { 'partial': functools_partial, - 'wraps': _return_first_param, + 'wraps': _functools_wraps, + #'wraps': _return_first_param, }, '_weakref': { 'proxy': _return_first_param, diff --git a/test/test_evaluate/test_signature.py b/test/test_evaluate/test_signature.py index 49a17dd3..5a4e2423 100644 --- a/test/test_evaluate/test_signature.py +++ b/test/test_evaluate/test_signature.py @@ -179,6 +179,41 @@ def test_pow_signature(Script): 'pow(x: int, y: int, /) -> Any'} +@pytest.mark.parametrize( + 'code, signature', [ + [dedent(''' + import functools + def f(x): + pass + def x(f): + @functools.wraps(f) + def wrapper(*args): + # Have no arguments here, but because of wraps, the signature + # should still be f's. + return f(*args) + return wrapper + + x(f)('''), 'f(x, /)'], + [dedent(''' + import functools + def f(x): + pass + def x(f): + @functools.wraps(f) + def wrapper(): + # Have no arguments here, but because of wraps, the signature + # should still be f's. + return 1 + return wrapper + + x(f)('''), 'f()'], + ] +) +def test_wraps_signature(Script, code, signature, skip_pre_python35): + sigs = Script(code).call_signatures() + assert {sig._signature.to_string() for sig in sigs} == {signature} + + @pytest.mark.parametrize( 'start, start_params', [ ['@dataclass\nclass X:', []],