forked from VimPlug/jedi
A new approach of getting arguments
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
Reference in New Issue
Block a user