mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-08 06:44:46 +08:00
Start getting signature inferring working
This commit is contained in:
@@ -150,8 +150,18 @@ class TreeNameDefinition(AbstractTreeName):
|
|||||||
return self._API_TYPES.get(definition.type, 'statement')
|
return self._API_TYPES.get(definition.type, 'statement')
|
||||||
|
|
||||||
|
|
||||||
class ParamNameInterface(object):
|
class _ParamMixin(object):
|
||||||
api_type = u'param'
|
def maybe_positional_argument(self, include_star=True):
|
||||||
|
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||||
|
if include_star:
|
||||||
|
options.append(Parameter.VAR_POSITIONAL)
|
||||||
|
return self.get_kind() in options
|
||||||
|
|
||||||
|
def maybe_keyword_argument(self, include_stars=True):
|
||||||
|
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||||
|
if include_stars:
|
||||||
|
options.append(Parameter.VAR_KEYWORD)
|
||||||
|
return self.get_kind() in options
|
||||||
|
|
||||||
def _kind_string(self):
|
def _kind_string(self):
|
||||||
kind = self.get_kind()
|
kind = self.get_kind()
|
||||||
@@ -161,6 +171,10 @@ class ParamNameInterface(object):
|
|||||||
return '**'
|
return '**'
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class ParamNameInterface(_ParamMixin):
|
||||||
|
api_type = u'param'
|
||||||
|
|
||||||
def get_kind(self):
|
def get_kind(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -247,6 +261,17 @@ class ParamName(BaseTreeParamName):
|
|||||||
return params[param_node.position_index]
|
return params[param_node.position_index]
|
||||||
|
|
||||||
|
|
||||||
|
class ParamNameWrapper(_ParamMixin):
|
||||||
|
def __init__(self, param_name):
|
||||||
|
self._wrapped_param_name = param_name
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._wrapped_param_name, name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
|
||||||
|
|
||||||
|
|
||||||
class ImportName(AbstractNameDefinition):
|
class ImportName(AbstractNameDefinition):
|
||||||
start_pos = (1, 0)
|
start_pos = (1, 0)
|
||||||
_level = 0
|
_level = 0
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from jedi._compatibility import Parameter
|
from jedi._compatibility import Parameter
|
||||||
|
from jedi.evaluate.utils import to_list
|
||||||
|
from jedi.evaluate.names import ParamNameWrapper
|
||||||
|
|
||||||
|
|
||||||
class _SignatureMixin(object):
|
class _SignatureMixin(object):
|
||||||
@@ -77,6 +79,113 @@ class TreeSignature(AbstractSignature):
|
|||||||
return ''
|
return ''
|
||||||
return a.get_code(include_prefix=False)
|
return a.get_code(include_prefix=False)
|
||||||
|
|
||||||
|
@to_list
|
||||||
|
def get_param_names(self):
|
||||||
|
used_names = set()
|
||||||
|
kwarg_params = []
|
||||||
|
for param_name in super(TreeSignature, self).get_param_names():
|
||||||
|
kind = param_name.get_kind()
|
||||||
|
if kind == Parameter.VAR_POSITIONAL:
|
||||||
|
for param_names in _iter_nodes_for_param(param_name, star_count=1):
|
||||||
|
for p in param_names:
|
||||||
|
yield p
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
yield param_name
|
||||||
|
elif kind == Parameter.VAR_KEYWORD:
|
||||||
|
kwarg_params.append(param_name)
|
||||||
|
else:
|
||||||
|
if param_name.maybe_keyword_argument():
|
||||||
|
used_names.add(param_name.string_name)
|
||||||
|
yield param_name
|
||||||
|
|
||||||
|
for param_name in kwarg_params:
|
||||||
|
for param_names in _iter_nodes_for_param(param_name, star_count=2):
|
||||||
|
for p in param_names:
|
||||||
|
if p.string_name not in used_names:
|
||||||
|
used_names.add(param_name.string_name)
|
||||||
|
yield p
|
||||||
|
print(p)
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_nodes_for_param(param_name, star_count):
|
||||||
|
from jedi.evaluate.syntax_tree import eval_trailer
|
||||||
|
from jedi.evaluate.names import TreeNameDefinition
|
||||||
|
from parso.python.tree import search_ancestor
|
||||||
|
from jedi.evaluate.arguments import TreeArguments
|
||||||
|
|
||||||
|
execution_context = param_name.parent_context
|
||||||
|
function_node = execution_context.tree_node
|
||||||
|
module_node = function_node.get_root_node()
|
||||||
|
start = function_node.children[-1].start_pos
|
||||||
|
end = function_node.children[-1].end_pos
|
||||||
|
for name in module_node.get_used_names().get(param_name.string_name):
|
||||||
|
if start <= name.start_pos < end:
|
||||||
|
# Is used in the function.
|
||||||
|
argument = name.parent
|
||||||
|
if argument.type == 'argument' and argument.children[0] == '*' * star_count:
|
||||||
|
# No support for Python <= 3.4 here, but they are end-of-life
|
||||||
|
# anyway.
|
||||||
|
trailer = search_ancestor(argument, 'trailer')
|
||||||
|
if trailer is not None:
|
||||||
|
atom_expr = trailer.parent
|
||||||
|
context = execution_context.create_context(atom_expr)
|
||||||
|
found = TreeNameDefinition(context, name).goto()
|
||||||
|
if any(param_name.parent_context == p.parent_context
|
||||||
|
and param_name.start_pos == p.start_pos
|
||||||
|
for p in found):
|
||||||
|
index = atom_expr.children[0] == 'await'
|
||||||
|
# Eval atom first
|
||||||
|
contexts = context.eval_node(atom_expr.children[index])
|
||||||
|
for trailer2 in atom_expr.children[index + 1:]:
|
||||||
|
if trailer == trailer2:
|
||||||
|
break
|
||||||
|
contexts = eval_trailer(context, contexts, trailer2)
|
||||||
|
args = TreeArguments(
|
||||||
|
evaluator=execution_context.evaluator,
|
||||||
|
context=context,
|
||||||
|
argument_node=trailer.children[1],
|
||||||
|
trailer=trailer,
|
||||||
|
)
|
||||||
|
for c in contexts:
|
||||||
|
yield _remove_given_params(args, c.get_param_names(), star_count)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_given_params(arguments, param_names, star_count):
|
||||||
|
count = 0
|
||||||
|
used_keys = set()
|
||||||
|
for key, _ in arguments.unpack():
|
||||||
|
if key is None:
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
used_keys.add(key)
|
||||||
|
|
||||||
|
for p in param_names:
|
||||||
|
kind = p.get_kind()
|
||||||
|
if count and kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
|
||||||
|
count -= 1
|
||||||
|
continue
|
||||||
|
if p.string_name in used_keys \
|
||||||
|
and kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# TODO recurse on *args/**kwargs
|
||||||
|
if star_count == 1 and p.maybe_positional_argument():
|
||||||
|
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
|
||||||
|
elif star_count == 2 and p.maybe_keyword_argument():
|
||||||
|
yield ParamNameFixedKind(p, Parameter.KEYWORD_ONLY)
|
||||||
|
|
||||||
|
|
||||||
|
class ParamNameFixedKind(ParamNameWrapper):
|
||||||
|
def __init__(self, param_name, new_kind):
|
||||||
|
super(ParamNameFixedKind, self).__init__(param_name)
|
||||||
|
self._new_kind = new_kind
|
||||||
|
|
||||||
|
def get_kind(self):
|
||||||
|
return self._new_kind
|
||||||
|
|
||||||
|
|
||||||
class BuiltinSignature(AbstractSignature):
|
class BuiltinSignature(AbstractSignature):
|
||||||
def __init__(self, context, return_string, is_bound=False):
|
def __init__(self, context, return_string, is_bound=False):
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ c = functools.partial(func, 1, c=2)
|
|||||||
d = functools.partial()
|
d = functools.partial()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'code, expected', [
|
'code, expected', [
|
||||||
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
||||||
@@ -91,6 +92,38 @@ def test_tree_signature(Script, environment, code, expected):
|
|||||||
assert expected == sig._signature.to_string()
|
assert expected == sig._signature.to_string()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'combination, expected', [
|
||||||
|
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
|
||||||
|
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
|
||||||
|
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),
|
||||||
|
('combined_redirect(simple3, simple)', 'a, x: int, /, *, a, b, c'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_nested_signatures(Script, environment, combination, expected):
|
||||||
|
code = dedent('''
|
||||||
|
def simple(a, b, *, c): ...
|
||||||
|
def simple2(x): ...
|
||||||
|
def simple3(a, x: int): ...
|
||||||
|
def a(a, b, *args): ...
|
||||||
|
def kw(a, b, *, c, **kwargs): ...
|
||||||
|
def akw(a, b, *args, **kwargs): ...
|
||||||
|
|
||||||
|
def no_redirect(func):
|
||||||
|
return lambda *args, **kwargs: func(1)
|
||||||
|
def full_redirect(func):
|
||||||
|
return lambda *args, **kwargs: func(1, *args, **kwargs)
|
||||||
|
def full_redirect(func):
|
||||||
|
return lambda *args, **kwargs: func(, *args, **kwargs)
|
||||||
|
def combined_redirect(func1, func2):
|
||||||
|
return lambda *args, **kwargs: func1(*args) + func2(**kwargs)
|
||||||
|
''')
|
||||||
|
code += 'z = ' + combination + '\nz('
|
||||||
|
sig, = Script(code).call_signatures()
|
||||||
|
computed = sig._signature.to_string()
|
||||||
|
assert '<lambda>(' + expected + ')' == computed
|
||||||
|
|
||||||
|
|
||||||
def test_pow_signature(Script):
|
def test_pow_signature(Script):
|
||||||
# See github #1357
|
# See github #1357
|
||||||
sigs = Script('pow(').call_signatures()
|
sigs = Script('pow(').call_signatures()
|
||||||
|
|||||||
Reference in New Issue
Block a user