From 9aa8f6bcf2c04e844b12eb0a9edca55a620a410d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 23 May 2019 01:36:51 +0200 Subject: [PATCH] Better signature calculation --- jedi/evaluate/compiled/access.py | 7 ++-- jedi/evaluate/compiled/context.py | 33 +++++++++++------ jedi/evaluate/signature.py | 55 ++++++++++++++++++++++------ test/test_evaluate/test_signature.py | 14 +++---- 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index 20e9df30..84b7b9b4 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -92,7 +92,7 @@ def safe_getattr(obj, name, default=_sentinel): SignatureParam = namedtuple( 'SignatureParam', - 'name has_default default has_annotation annotation kind_name' + 'name has_default default default_string has_annotation annotation annotation_string kind_name' ) @@ -382,15 +382,14 @@ class DirectObjectAccess(object): name=p.name, has_default=p.default is not p.empty, default=self._create_access_path(p.default), + default_string=str(p.default), has_annotation=p.annotation is not p.empty, annotation=self._create_access_path(p.annotation), + annotation_string=str(p.default), kind_name=str(p.kind) ) for p in self._get_signature().parameters.values() ] - def get_signature_text(self): - return str(self._get_signature()) - def _get_signature(self): obj = self._obj if py_version < 33: diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index e4b3061f..63dcbb50 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -123,21 +123,15 @@ class CompiledObject(Context): if self.access_handle.ismethoddescriptor(): tokens.insert(0, 'self') for p in tokens: - parts = p.strip().split('=') - yield UnresolvableParamName(self, parts[0]) + name, _, default = p.strip().partition('=') + yield UnresolvableParamName(self, name, default) else: for signature_param in signature_params: yield SignatureParamName(self, signature_param) - def get_signature_text(self): - try: - return self.access_handle.get_signature_text() - except ValueError: - params_str, ret = self._parse_function_doc() - return '(' + params_str + ')' + (ret and ' -> ' + ret) - def get_signatures(self): - return [BuiltinSignature(self)] + _, return_string = self._parse_function_doc() + return [BuiltinSignature(self, return_string)] def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr()) @@ -295,6 +289,14 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface): def string_name(self): return self._signature_param.name + def to_string(self): + s = self.string_name + if self._signature_param.has_annotation: + s += ': ' + self._signature_param.annotation_string + if self._signature_param.has_default: + s += '=' + self._signature_param.default_string + return s + def get_kind(self): return getattr(Parameter, self._signature_param.kind_name) @@ -313,16 +315,23 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface): return contexts -class UnresolvableParamName(AbstractNameDefinition): +class UnresolvableParamName(AbstractNameDefinition, ParamNameInterface): api_type = u'param' - def __init__(self, compiled_obj, name): + def __init__(self, compiled_obj, name, default): self.parent_context = compiled_obj.parent_context self.string_name = name + self._default = default def get_kind(self): return Parameter.POSITIONAL_ONLY + def to_string(self): + string = self.string_name + if self._default: + string += '=' + self._default + return string + def infer(self): return NO_CONTEXTS diff --git a/jedi/evaluate/signature.py b/jedi/evaluate/signature.py index ffd1ea80..1138617d 100644 --- a/jedi/evaluate/signature.py +++ b/jedi/evaluate/signature.py @@ -1,3 +1,6 @@ +from jedi._compatibility import Parameter + + class AbstractSignature(object): def __init__(self, context, is_bound=False): self.context = context @@ -8,15 +11,33 @@ class AbstractSignature(object): return self.context.name @property - def annotation(self): - return None + def annotation_string(self): + return '' def to_string(self): - param_code = ', '.join(n.to_string() for n in self.get_param_names()) - s = self.name.string_name + '(' + param_code + ')' - annotation = self.annotation - if annotation is not None: - s += ' -> ' + annotation.get_code(include_prefix=False) + def param_strings(): + is_positional = False + is_kw_only = False + for n in self.get_param_names(): + kind = n.get_kind() + is_positional |= kind == Parameter.POSITIONAL_ONLY + if is_positional and kind != Parameter.POSITIONAL_ONLY: + yield '/' + is_positional = False + + if kind == Parameter.KEYWORD_ONLY and not is_kw_only: + yield '*' + is_kw_only = True + + yield n.to_string() + + if is_positional: + yield '/' + + s = self.name.string_name + '(' + ', '.join(param_strings()) + ')' + annotation = self.annotation_string + if annotation: + s += ' -> ' + annotation return s def bind(self, context): @@ -38,21 +59,33 @@ class TreeSignature(AbstractSignature): return TreeSignature(context, self._function_context, is_bound=True) @property - def annotation(self): + def _annotation(self): # Classes don't need annotations, even if __init__ has one. They always # return themselves. if self.context.is_class(): return None return self._function_context.tree_node.annotation + @property + def annotation_string(self): + a = self._annotation + if a is None: + return '' + return a.get_code(include_prefix=False) + class BuiltinSignature(AbstractSignature): + def __init__(self, context, return_string, is_bound=False): + super(BuiltinSignature, self).__init__(context, is_bound) + self._return_string = return_string + + @property + def annotation_string(self): + return self._return_string + @property def _function_context(self): return self.context - def to_string(self): - return self.name.string_name + self.context.get_signature_text() - def bind(self, context): raise NotImplementedError('pls implement, need test case, %s' % context) diff --git a/test/test_evaluate/test_signature.py b/test/test_evaluate/test_signature.py index 14a205ad..106270ce 100644 --- a/test/test_evaluate/test_signature.py +++ b/test/test_evaluate/test_signature.py @@ -6,19 +6,18 @@ from jedi.evaluate.gradual.conversion import stub_to_actual_context_set @pytest.mark.parametrize( 'code, sig, names, op, version', [ - ('import math; math.cos', 'cos(x)', ['x'], lt, (3, 7)), - ('import math; math.cos', 'cos(x, /)', ['x'], ge, (3, 7)), + ('import math; math.cos', 'cos(x, /)', ['x'], ge, (2, 7)), - ('next', 'next(iterator, default=None)', ['iterator', 'default'], ge, (2, 7)), + ('next', 'next(iterator, default=None, /)', ['iterator', 'default'], ge, (2, 7)), - ('pow', 'pow(x, y, z=None) -> number', ['x', 'y', 'z'], lt, (3, 5)), + ('pow', 'pow(x, y, z=None, /) -> number', ['x', 'y', 'z'], lt, (3, 5)), ('pow', 'pow(x, y, z=None, /)', ['x', 'y', 'z'], ge, (3, 5)), - ('bytes.partition', 'partition(self, sep) -> (head, sep, tail)', ['self', 'sep'], lt, (3, 5)), + ('bytes.partition', 'partition(self, sep, /) -> (head, sep, tail)', ['self', 'sep'], lt, (3, 5)), ('bytes.partition', 'partition(self, sep, /)', ['self', 'sep'], ge, (3, 5)), - ('bytes().partition', 'partition(sep) -> (head, sep, tail)', ['sep'], lt, (3, 5)), - ('bytes().partition', 'partition(self, sep, /)', ['sep'], ge, (3, 5)), + ('bytes().partition', 'partition(sep, /) -> (head, sep, tail)', ['sep'], lt, (3, 5)), + ('bytes().partition', 'partition(sep, /)', ['sep'], ge, (3, 5)), ] ) def test_compiled_signature(Script, environment, code, sig, names, op, version): @@ -31,4 +30,3 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version): signature, = compiled.get_signatures() assert signature.to_string() == sig assert [n.string_name for n in signature.get_param_names()] == names - assert signature.annotation is None