diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index db74d36a..8775aea3 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -175,6 +175,18 @@ def _force_unicode_decorator(func): return lambda *args, **kwargs: force_unicode(func(*args, **kwargs)) +def get_api_type(obj): + if inspect.isclass(obj): + return u'class' + elif inspect.ismodule(obj): + return u'module' + elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ + or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): + return u'function' + # Everything else... + return u'instance' + + class DirectObjectAccess(object): def __init__(self, evaluator, obj): self._evaluator = evaluator @@ -352,16 +364,7 @@ class DirectObjectAccess(object): raise ValueError("Object is type %s and not simple" % type(self._obj)) def get_api_type(self): - obj = self._obj - if self.is_class(): - return u'class' - elif inspect.ismodule(obj): - return u'module' - elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ - or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): - return u'function' - # Everything else... - return u'instance' + return get_api_type(self._obj) def get_access_path_tuples(self): accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()] diff --git a/jedi/evaluate/compiled/mixed.py b/jedi/evaluate/compiled/mixed.py index e611cfa3..9c419e2f 100644 --- a/jedi/evaluate/compiled/mixed.py +++ b/jedi/evaluate/compiled/mixed.py @@ -18,7 +18,7 @@ from jedi.evaluate.context import ModuleContext from jedi.evaluate.cache import evaluator_function_cache from jedi.evaluate.compiled.getattr_static import getattr_static from jedi.evaluate.compiled.access import compiled_objects_cache, \ - ALLOWED_GETITEM_TYPES + ALLOWED_GETITEM_TYPES, get_api_type from jedi.evaluate.compiled.context import create_cached_compiled_object from jedi.evaluate.gradual.conversion import to_stub @@ -50,6 +50,11 @@ class MixedObject(ContextWrapper): def get_filters(self, *args, **kwargs): yield MixedObjectFilter(self.evaluator, self) + def get_signatures(self): + # Prefer `inspect.signature` over somehow analyzing Python code. It + # should be very precise, especially for stuff like `partial`. + return self.compiled_object.get_signatures() + def py__call__(self, arguments): return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments) @@ -151,6 +156,7 @@ def _get_object_to_check(python_object): def _find_syntax_node_name(evaluator, python_object): + original_object = python_object try: python_object = _get_object_to_check(python_object) path = inspect.getsourcefile(python_object) @@ -214,7 +220,13 @@ def _find_syntax_node_name(evaluator, python_object): # completions at some points but will lead to mostly correct type # inference, because people tend to define a public name in a module only # once. - return module_node, names[-1].parent, file_io, code_lines + tree_node = names[-1].parent + if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance': + # If an instance is given and we're landing on a function (e.g. + # partial in 3.5), something is completely wrong and we should not + # return that. + return None + return module_node, tree_node, file_io, code_lines @compiled_objects_cache('mixed_cache') diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index abee6b78..d4d17d79 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -229,7 +229,7 @@ def test_property_error_newstyle(): assert lst == [] -def test_param_completion(): +def test_param_completion(skip_python2): def foo(bar): pass @@ -275,7 +275,7 @@ def test_completion_param_annotations(): assert d.name == 'bytes' -def test_keyword_argument(): +def test_keyword_argument(skip_python2): def f(some_keyword_argument): pass @@ -440,7 +440,33 @@ def test__wrapped__(): # Apparently the function starts on the line where the decorator starts. assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1 + @pytest.mark.parametrize('module_name', ['sys', 'time']) def test_core_module_completes(module_name): module = import_module(module_name) assert jedi.Interpreter(module_name + '.\n', [locals()]).completions() + + +@pytest.mark.parametrize( + 'code, expected, index', [ + ('a(', ['a', 'b', 'c'], 0), + ('b(', ['b', 'c'], 0), + # Might or might not be correct, because c is given as a keyword + # argument as well, but that is just what inspect.signature returns. + ('c(', ['b', 'c'], 0), + ] +) +def test_partial_signatures(code, expected, index, skip_python2): + import functools + + def func(a, b, c): + pass + + a = functools.partial(func) + b = functools.partial(func, 1) + c = functools.partial(func, 1, c=2) + + sig, = jedi.Interpreter(code, [locals()]).call_signatures() + assert sig.name == 'partial' + assert [p.name for p in sig.params] == expected + assert index == sig.index