diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 0031cde9..7c0d704d 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -115,13 +115,19 @@ class CompiledObject(Context): return inspect.getdoc(self.obj) or '' def get_param_names(self): - params_str, ret = self._parse_function_doc() - tokens = params_str.split(',') - if inspect.ismethoddescriptor(self.obj): - tokens.insert(0, 'self') - for p in tokens: - parts = p.strip().split('=') - yield UnresolvableParamName(self, parts[0]) + try: + signature = inspect.signature(self.obj) + except ValueError: # Has no signature + params_str, ret = self._parse_function_doc() + tokens = params_str.split(',') + if inspect.ismethoddescriptor(self.obj): + tokens.insert(0, 'self') + for p in tokens: + parts = p.strip().split('=') + yield UnresolvableParamName(self, parts[0]) + else: + for signature_param in signature.parameters.values(): + yield SignatureParamName(self, signature_param) def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, repr(self.obj)) @@ -276,6 +282,29 @@ class CompiledName(AbstractNameDefinition): return [_create_from_name(self._evaluator, module, self.parent_context, self.string_name)] +class SignatureParamName(AbstractNameDefinition): + api_type = 'param' + + def __init__(self, compiled_obj, signature_param): + self.parent_context = compiled_obj.parent_context + self._signature_param = signature_param + + @property + def string_name(self): + return self._signature_param.name + + def infer(self): + p = self._signature_param + evaluator = self.parent_context.evaluator + types = set() + if p.default is not p.empty: + types.add(create(evaluator, p.default)) + if p.annotation is not p.empty: + annotation = create(evaluator, p.annotation) + types |= annotation.execute_evaluated() + return types + + class UnresolvableParamName(AbstractNameDefinition): api_type = 'param' diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 8d25d4be..49e6f4de 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -1,9 +1,10 @@ """ Tests of ``jedi.api.Interpreter``. """ +import pytest import jedi -from jedi._compatibility import is_py33 +from jedi._compatibility import is_py33, exec_function, py_version from jedi.evaluate.compiled import mixed @@ -225,3 +226,29 @@ def test_endless_yield(): # If iterating over lists it should not be possible to take an extremely # long time. _assert_interpreter_complete('list(lst)[9000].rea', locals(), ['real']) + + +@pytest.mark.skipif('py_version < 33', reason='inspect.signature was created in 3.3.') +def test_completion_params(): + foo = lambda a, b=3: None + + script = jedi.Interpreter('foo', [locals()]) + c, = script.completions() + assert [p.name for p in c.params] == ['a', 'b'] + assert c.params[0]._goto_definitions() == [] + t, = c.params[1]._goto_definitions() + assert t.name == 'int' + + +@pytest.mark.skipif('py_version < 33', reason='inspect.signature was created in 3.3.') +def test_completion_param_annotations(): + # Need to define this function not directly in Python. Otherwise Jedi is to + # clever and uses the Python code instead of the signature object. + code = 'def foo(a: 1, b: str, c: int = 1.0): pass' + exec_function(code, locals()) + script = jedi.Interpreter('foo', [locals()]) + c, = script.completions() + a, b, c = c.params + assert a._goto_definitions() == [] + assert [d.name for d in b._goto_definitions()] == ['str'] + assert [d.name for d in c._goto_definitions()] == ['int', 'float']