diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 387e4b18..d51356d6 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -7,6 +7,7 @@ import os import re import pkgutil import warnings +import inspect try: import importlib except ImportError: @@ -174,7 +175,6 @@ def _iter_modules(paths, prefix=''): yielded = {} - import inspect try: filenames = os.listdir(importer.path) except OSError: @@ -457,3 +457,14 @@ def pickle_load(file): def pickle_dump(data, file): pickle.dump(data, file, protocol=_PICKLE_PROTOCOL) + + +try: + from inspect import Parameter +except ImportError: + class Parameter(object): + POSITIONAL_ONLY = object() + POSITIONAL_OR_KEYWORD = object() + VAR_POSITIONAL = object() + KEYWORD_ONLY = object() + VAR_KEYWORD = object() diff --git a/jedi/api/completion.py b/jedi/api/completion.py index dcf18ee3..1d49b587 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -2,6 +2,7 @@ from parso.python import token from parso.python import tree from parso.tree import search_ancestor, Leaf +from jedi._compatibility import Parameter from jedi import debug from jedi import settings from jedi.api import classes @@ -18,17 +19,9 @@ def get_call_signature_param_names(call_signatures): for call_sig in call_signatures: for p in call_sig.params: # Allow protected access, because it's a public API. - tree_name = p._name.tree_name - # Compiled modules typically don't allow keyword arguments. - if tree_name is not None: - # Allow access on _definition here, because it's a - # public API and we don't want to make the internal - # Name object public. - tree_param = tree.search_ancestor(tree_name, 'param') - if tree_param.star_count != 0: # no *args/**kwargs - continue - - yield p._name + if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD, + Parameter.KEYWORD_ONLY): + yield p._name def filter_names(evaluator, completion_names, stack, like_name): diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index 171873ac..97a8dd0b 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -99,7 +99,10 @@ def safe_getattr(obj, name, default=_sentinel): return attr -SignatureParam = namedtuple('SignatureParam', 'name has_default default has_annotation annotation') +SignatureParam = namedtuple( + 'SignatureParam', + 'name has_default default has_annotation annotation kind_name' +) def compiled_objects_cache(attribute_name): @@ -413,6 +416,7 @@ class DirectObjectAccess(object): default=self._create_access_path(p.default), has_annotation=p.annotation is not p.empty, annotation=self._create_access_path(p.annotation), + kind_name=str(p.kind) ) for p in signature.parameters.values() ] diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 25949af7..f81509d7 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -5,7 +5,7 @@ import re from functools import partial from jedi import debug -from jedi._compatibility import force_unicode +from jedi._compatibility import force_unicode, Parameter from jedi.cache import underscore_memoization, memoize_method from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ ContextNameMixin @@ -248,6 +248,12 @@ class SignatureParamName(AbstractNameDefinition): def string_name(self): return self._signature_param.name + def get_kind(self): + return getattr(Parameter, self._signature_param.kind_name) + + def is_keyword_param(self): + return self._signature_param + def infer(self): p = self._signature_param evaluator = self.parent_context.evaluator @@ -267,6 +273,9 @@ class UnresolvableParamName(AbstractNameDefinition): self.parent_context = compiled_obj.parent_context self.string_name = name + def get_kind(self): + return Parameter.POSITIONAL_ONLY + def infer(self): return ContextSet() diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index db66e48a..4294f2a6 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -6,7 +6,7 @@ from abc import abstractmethod from parso.tree import search_ancestor -from jedi._compatibility import use_metaclass +from jedi._compatibility import use_metaclass, Parameter from jedi.cache import memoize_method from jedi.evaluate import flow_analysis from jedi.evaluate.base_context import ContextSet, Context @@ -122,6 +122,22 @@ class ParamName(AbstractTreeName): self.parent_context = parent_context self.tree_name = tree_name + def get_kind(self): + tree_param = search_ancestor(self.tree_name, 'param') + if tree_param.star_count == 1: # *args + return Parameter.VAR_POSITIONAL + if tree_param.star_count == 2: # **kwargs + return Parameter.VAR_KEYWORD + + parent = tree_param.parent + for p in parent.children: + if p.type == 'param': + if p.star_count: + return Parameter.KEYWORD_ONLY + if p == tree_param: + break + return Parameter.POSITIONAL_OR_KEYWORD + def infer(self): return self.get_param().infer()