From fae2c8c060a7650bf3ad537cf993b92c2d04e9a1 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 28 Jul 2019 17:41:28 +0200 Subject: [PATCH] Move args resolving to a different file --- jedi/evaluate/signature.py | 170 +--------------------------------- jedi/evaluate/star_args.py | 181 +++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 168 deletions(-) create mode 100644 jedi/evaluate/star_args.py diff --git a/jedi/evaluate/signature.py b/jedi/evaluate/signature.py index b9223fef..b85d9e06 100644 --- a/jedi/evaluate/signature.py +++ b/jedi/evaluate/signature.py @@ -1,6 +1,4 @@ from jedi._compatibility import Parameter -from jedi.evaluate.utils import to_list -from jedi.evaluate.names import ParamNameWrapper class _SignatureMixin(object): @@ -80,172 +78,8 @@ class TreeSignature(AbstractSignature): return a.get_code(include_prefix=False) def get_param_names(self): - return _process_params(super(TreeSignature, self).get_param_names()) - - -def _iter_nodes_for_param(param_name): - 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] == '*' * param_name.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: # Make sure we're in a function - context = execution_context.create_context(trailer) - if _goes_to_param_name(param_name, context, name): - contexts = _to_callables(context, trailer) - - args = TreeArguments.create_cached( - execution_context.evaluator, - context=context, - argument_node=trailer.children[1], - trailer=trailer, - ) - for c in contexts: - yield c, args - else: - 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): - 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: - if count and p.maybe_positional_argument(): - count -= 1 - continue - if p.string_name in used_keys and p.maybe_keyword_argument(): - continue - yield p - - -def _process_params(param_names, star_count=3): # default means both * and ** - used_names = set() - kw_only_params = [] - arg_funcs = [] - kwarg_funcs = [] - kwarg_names = [] - longest_param_names = () - for p in param_names: - kind = p.get_kind() - if kind == Parameter.VAR_POSITIONAL: - if star_count & 1: - arg_funcs = list(_iter_nodes_for_param(p)) - if not arg_funcs: - yield p - 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) - elif star_count == 2: - yield ParamNameFixedKind(p, Parameter.KEYWORD_ONLY) - 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 - - 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): - 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 + from jedi.evaluate.star_args import process_params + return process_params(super(TreeSignature, self).get_param_names()) class BuiltinSignature(AbstractSignature): diff --git a/jedi/evaluate/star_args.py b/jedi/evaluate/star_args.py new file mode 100644 index 00000000..c5a603e5 --- /dev/null +++ b/jedi/evaluate/star_args.py @@ -0,0 +1,181 @@ +""" +This module is responsible for evaluating *args and **kwargs for signatures. + +This means for example in this case:: + + def foo(a, b, c): ... + + def bar(*args): + return foo(1, *args) + +The signature here for bar should be `bar(b, c)` instead of bar(*args). +""" + +from jedi._compatibility import Parameter +from jedi.evaluate.names import ParamNameWrapper + + +def _iter_nodes_for_param(param_name): + 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] == '*' * param_name.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: # Make sure we're in a function + context = execution_context.create_context(trailer) + if _goes_to_param_name(param_name, context, name): + contexts = _to_callables(context, trailer) + + args = TreeArguments.create_cached( + execution_context.evaluator, + context=context, + argument_node=trailer.children[1], + trailer=trailer, + ) + for c in contexts: + yield c, args + else: + 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): + 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: + if count and p.maybe_positional_argument(): + count -= 1 + continue + if p.string_name in used_keys and p.maybe_keyword_argument(): + continue + yield p + + +def process_params(param_names, star_count=3): # default means both * and ** + used_names = set() + kw_only_params = [] + arg_funcs = [] + kwarg_funcs = [] + kwarg_names = [] + longest_param_names = () + for p in param_names: + kind = p.get_kind() + if kind == Parameter.VAR_POSITIONAL: + if star_count & 1: + arg_funcs = list(_iter_nodes_for_param(p)) + if not arg_funcs: + yield p + 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) + elif star_count == 2: + yield ParamNameFixedKind(p, Parameter.KEYWORD_ONLY) + 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 + + 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): + 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 +