diff --git a/jedi/inference/gradual/conversion.py b/jedi/inference/gradual/conversion.py index 0700716d..4eff0f40 100644 --- a/jedi/inference/gradual/conversion.py +++ b/jedi/inference/gradual/conversion.py @@ -18,6 +18,7 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False): was_instance = stub_value.is_instance() if was_instance: + arguments = getattr(stub_value, '_arguments', None) stub_value = stub_value.py__class__() qualified_names = stub_value.get_qualified_names() @@ -30,11 +31,12 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False): method_name = qualified_names[-1] qualified_names = qualified_names[:-1] was_instance = True + arguments = None values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled) if was_instance: values = ValueSet.from_sets( - c.execute_with_values() + c.execute_with_values() if arguments is None else c.execute(arguments) for c in values if c.is_class() ) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 382b8ced..8250b465 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -474,11 +474,10 @@ def collections_namedtuple(value, arguments, callback): class PartialObject(ValueWrapper): def __init__(self, actual_value, arguments, instance=None): super(PartialObject, self).__init__(actual_value) - self._actual_value = actual_value self._arguments = arguments self._instance = instance - def _get_function(self, unpacked_arguments): + def _get_functions(self, unpacked_arguments): key, lazy_value = next(unpacked_arguments, (None, None)) if key is not None or lazy_value is None: debug.warning("Partial should have a proper function %s", self._arguments) @@ -487,8 +486,8 @@ class PartialObject(ValueWrapper): def get_signatures(self): unpacked_arguments = self._arguments.unpack() - func = self._get_function(unpacked_arguments) - if func is None: + funcs = self._get_functions(unpacked_arguments) + if funcs is None: return [] arg_count = 0 @@ -500,17 +499,30 @@ class PartialObject(ValueWrapper): arg_count += 1 else: keys.add(key) - return [PartialSignature(s, arg_count, keys) for s in func.get_signatures()] + return [PartialSignature(s, arg_count, keys) for s in funcs.get_signatures()] def py__call__(self, arguments): - func = self._get_function(self._arguments.unpack()) - if func is None: + funcs = self._get_functions(self._arguments.unpack()) + if funcs is None: return NO_VALUES - return func.execute( + return funcs.execute( MergedPartialArguments(self._arguments, arguments, self._instance) ) + def py__doc__(self): + """ + In CPython partial does not replace the docstring. However we are still + imitating it here, because we want this docstring to be worth something + for the user. + """ + callables = self._get_functions(self._arguments.unpack()) + if callables is None: + return '' + for callable_ in callables: + return callable_.py__doc__() + return '' + def py__get__(self, instance, class_value): return ValueSet([self]) @@ -519,7 +531,7 @@ class PartialMethodObject(PartialObject): def py__get__(self, instance, class_value): if instance is None: return ValueSet([self]) - return ValueSet([PartialObject(self._actual_value, self._arguments, instance)]) + return ValueSet([PartialObject(self._wrapped_value, self._arguments, instance)]) class PartialSignature(SignatureWrapper): diff --git a/test/test_inference/test_docstring.py b/test/test_inference/test_docstring.py index 1bbf75d4..4d371f3f 100644 --- a/test/test_inference/test_docstring.py +++ b/test/test_inference/test_docstring.py @@ -422,6 +422,19 @@ def test_decorator(Script): assert d.docstring(raw=True) == 'Nice docstring' +def test_partial(Script): + code = dedent(''' + def foo(): + 'x y z' + from functools import partial + x = partial(foo) + x''') + + p1, p2 = Script(code).infer() + assert p1.docstring(raw=True) == 'x y z' + assert p2.docstring(raw=True) == 'x y z' + + def test_basic_str_init_signature(Script, disable_typeshed): # See GH #1414 and GH #1426 code = dedent('''