1
0
forked from VimPlug/jedi

A new approach of getting arguments

This commit is contained in:
Dave Halter
2019-07-28 17:31:17 +02:00
parent 6a480780f8
commit b4f2d82867
4 changed files with 125 additions and 77 deletions

View File

@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
from jedi.evaluate.names import ParamName, TreeNameDefinition from jedi.evaluate.names import ParamName, TreeNameDefinition
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
from jedi.evaluate.context import iterable from jedi.evaluate.context import iterable
from jedi.evaluate.cache import evaluator_as_method_param_cache
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
@@ -208,6 +209,11 @@ class TreeArguments(AbstractArguments):
self._evaluator = evaluator self._evaluator = evaluator
self.trailer = trailer # Can be None, e.g. in a class definition. self.trailer = trailer # Can be None, e.g. in a class definition.
@classmethod
@evaluator_as_method_param_cache()
def create_cached(cls, *args, **kwargs):
return cls(*args, **kwargs)
def unpack(self, funcdef=None): def unpack(self, funcdef=None):
named_args = [] named_args = []
for star_count, el in unpack_arglist(self.argument_node): for star_count, el in unpack_arglist(self.argument_node):

View File

@@ -187,6 +187,15 @@ class ParamNameInterface(_ParamMixin):
# clear what values would be allowed. # clear what values would be allowed.
return None return None
@property
def star_count(self):
kind = self.get_kind()
if kind == Parameter.VAR_POSITIONAL:
return 1
if kind == Parameter.VAR_KEYWORD:
return 2
return 0
class BaseTreeParamName(ParamNameInterface, AbstractTreeName): class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
annotation_node = None annotation_node = None

View File

@@ -79,41 +79,11 @@ 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): def get_param_names(self):
used_names = set() return _process_params(super(TreeSignature, self).get_param_names())
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:
itered = list(_iter_nodes_for_param(param_name, star_count=2))
if not itered:
yield param_name
for param_names in itered:
for p in param_names:
if p.string_name not in used_names or p.get_kind() == Parameter.VAR_KEYWORD:
used_names.add(p.string_name)
yield p
def _iter_nodes_for_param(param_name, star_count): def _iter_nodes_for_param(param_name):
from jedi.evaluate.syntax_tree import eval_trailer
from jedi.evaluate.names import TreeNameDefinition
from parso.python.tree import search_ancestor from parso.python.tree import search_ancestor
from jedi.evaluate.arguments import TreeArguments from jedi.evaluate.arguments import TreeArguments
@@ -124,41 +94,54 @@ def _iter_nodes_for_param(param_name, star_count):
end = function_node.children[-1].end_pos end = function_node.children[-1].end_pos
for name in module_node.get_used_names().get(param_name.string_name): for name in module_node.get_used_names().get(param_name.string_name):
if start <= name.start_pos < end: if start <= name.start_pos < end:
# Is used in the function. # Is used in the function
argument = name.parent argument = name.parent
if argument.type == 'argument' and argument.children[0] == '*' * star_count: if argument.type == 'argument' \
and argument.children[0] == '*' * param_name.star_count:
# No support for Python <= 3.4 here, but they are end-of-life # No support for Python <= 3.4 here, but they are end-of-life
# anyway. # anyway
trailer = search_ancestor(argument, 'trailer') trailer = search_ancestor(argument, 'trailer')
if trailer is not None: if trailer is not None: # Make sure we're in a function
atom_expr = trailer.parent context = execution_context.create_context(trailer)
context = execution_context.create_context(atom_expr) if _goes_to_param_name(param_name, context, name):
found = TreeNameDefinition(context, name).goto() contexts = _to_callables(context, trailer)
if any(param_name.parent_context == p.parent_context
and param_name.start_pos == p.start_pos args = TreeArguments.create_cached(
for p in found): execution_context.evaluator,
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, context=context,
argument_node=trailer.children[1], argument_node=trailer.children[1],
trailer=trailer, trailer=trailer,
) )
for c in contexts: for c in contexts:
yield list(_process_params( yield c, args
_remove_given_params(args, c.get_param_names()),
star_count,
))
else: else:
assert False assert False
def _goes_to_param_name(param_name, context, potential_name):
if potential_name.type != 'name':
return False
from jedi.evaluate.names import TreeNameDefinition
found = TreeNameDefinition(context, potential_name).goto()
return any(param_name.parent_context == p.parent_context
and param_name.start_pos == p.start_pos
for p in found)
def _to_callables(context, trailer):
from jedi.evaluate.syntax_tree import eval_trailer
atom_expr = trailer.parent
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)
return contexts
def _remove_given_params(arguments, param_names): def _remove_given_params(arguments, param_names):
count = 0 count = 0
used_keys = set() used_keys = set()
@@ -179,34 +162,82 @@ def _remove_given_params(arguments, param_names):
def _process_params(param_names, star_count=3): # default means both * and ** def _process_params(param_names, star_count=3): # default means both * and **
used_names = set() used_names = set()
param_names = list(param_names) kw_only_params = []
arg_funcs = []
kwarg_funcs = []
kwarg_names = []
longest_param_names = ()
for p in param_names: for p in param_names:
if star_count == 1 and p.maybe_positional_argument(): kind = p.get_kind()
if p.get_kind() == Parameter.VAR_POSITIONAL: if kind == Parameter.VAR_POSITIONAL:
for param_names in _iter_nodes_for_param(p, star_count=1): if star_count & 1:
for p in param_names: arg_funcs = list(_iter_nodes_for_param(p))
yield p if not arg_funcs:
break
else:
yield p yield p
else: elif p.get_kind() == Parameter.VAR_KEYWORD:
if star_count & 2:
kwarg_funcs = list(_iter_nodes_for_param(p))
if not kwarg_funcs:
kwarg_names.append(p)
elif kind == Parameter.KEYWORD_ONLY:
if star_count & 2:
kw_only_params.append(p)
elif kind == Parameter.POSITIONAL_ONLY:
if star_count & 1:
yield p
else:
if star_count == 1:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY) yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
elif star_count == 2 and p.maybe_keyword_argument(): elif star_count == 2:
if p.get_kind() == Parameter.VAR_KEYWORD:
itered = list(_iter_nodes_for_param(p, star_count=2))
if not itered:
# We were not able to resolve kwargs.
yield p
for param_names in itered:
for p in param_names:
if p.string_name not in used_names:
used_names.add(p.string_name)
yield p
else:
yield ParamNameFixedKind(p, Parameter.KEYWORD_ONLY) yield ParamNameFixedKind(p, Parameter.KEYWORD_ONLY)
elif star_count == 3: else:
yield p
for func_and_argument in arg_funcs:
func, arguments = func_and_argument
new_star_count = star_count
if func_and_argument in kwarg_funcs:
kwarg_funcs.remove(func_and_argument)
else:
new_star_count = 1
args_for_this_func = []
for p in _process_params(
list(_remove_given_params(
arguments,
func.get_param_names()
)), new_star_count):
if p.get_kind() == Parameter.VAR_KEYWORD:
kwarg_names.append(p)
elif p.get_kind() == Parameter.KEYWORD_ONLY:
kw_only_params.append(p)
else:
args_for_this_func.append(p)
if len(args_for_this_func) > len(longest_param_names):
longest_param_names = args_for_this_func
for p in longest_param_names:
if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
else:
yield p yield p
for p in kw_only_params:
yield p
for func, arguments in kwarg_funcs:
for p in _process_params(
list(_remove_given_params(
arguments,
func.get_param_names()
)), star_count=2):
if p.get_kind() != Parameter.KEYWORD_ONLY or not kwarg_names:
yield p
if kwarg_names:
yield kwarg_names[0]
return
class ParamNameFixedKind(ParamNameWrapper): class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind): def __init__(self, param_name, new_kind):

View File

@@ -94,6 +94,8 @@ def test_tree_signature(Script, environment, code, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
'combination, expected', [ 'combination, expected', [
('full_redirect(simple)', 'b, *, c'),
('combined_redirect(simple, simple2)', 'a, b, /, *, x'), ('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'), ('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'), ('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),