Format: text/x-python
⬇ Download File (7.7 KB)
   1  """
   2  This module is responsible for inferring *args and **kwargs for signatures.
   3  
   4  This means for example in this case::
   5  
   6      def foo(a, b, c): ...
   7  
   8      def bar(*args):
   9          return foo(1, *args)
  10  
  11  The signature here for bar should be `bar(b, c)` instead of bar(*args).
  12  """
  13  from inspect import Parameter
  14  
  15  from parso import tree
  16  
  17  from jedi.inference.utils import to_list
  18  from jedi.inference.names import ParamNameWrapper
  19  from jedi.inference.helpers import is_big_annoying_library
  20  
  21  
  22  def _iter_nodes_for_param(param_name):
  23      from parso.python.tree import search_ancestor
  24      from jedi.inference.arguments import TreeArguments
  25  
  26      execution_context = param_name.parent_context
  27      # Walk up the parso tree to get the FunctionNode we want. We use the parso
  28      # tree rather than going via the execution context so that we're agnostic of
  29      # the specific scope we're evaluating within (i.e: module or function,
  30      # etc.).
  31      function_node = tree.search_ancestor(param_name.tree_name, 'funcdef', 'lambdef')
  32      module_node = function_node.get_root_node()
  33      start = function_node.children[-1].start_pos
  34      end = function_node.children[-1].end_pos
  35      for name in module_node.get_used_names().get(param_name.string_name):
  36          if start <= name.start_pos < end:
  37              # Is used in the function
  38              argument = name.parent
  39              if argument.type == 'argument' \
  40                      and argument.children[0] == '*' * param_name.star_count:
  41                  trailer = search_ancestor(argument, 'trailer')
  42                  if trailer is not None:  # Make sure we're in a function
  43                      context = execution_context.create_context(trailer)
  44                      if _goes_to_param_name(param_name, context, name):
  45                          values = _to_callables(context, trailer)
  46  
  47                          args = TreeArguments.create_cached(
  48                              execution_context.inference_state,
  49                              context=context,
  50                              argument_node=trailer.children[1],
  51                              trailer=trailer,
  52                          )
  53                          for c in values:
  54                              yield c, args
  55  
  56  
  57  def _goes_to_param_name(param_name, context, potential_name):
  58      if potential_name.type != 'name':
  59          return False
  60      from jedi.inference.names import TreeNameDefinition
  61      found = TreeNameDefinition(context, potential_name).goto()
  62      return any(param_name.parent_context == p.parent_context
  63                 and param_name.start_pos == p.start_pos
  64                 for p in found)
  65  
  66  
  67  def _to_callables(context, trailer):
  68      from jedi.inference.syntax_tree import infer_trailer
  69  
  70      atom_expr = trailer.parent
  71      index = atom_expr.children[0] == 'await'
  72      # Infer atom first
  73      values = context.infer_node(atom_expr.children[index])
  74      for trailer2 in atom_expr.children[index + 1:]:
  75          if trailer == trailer2:
  76              break
  77          values = infer_trailer(context, values, trailer2)
  78      return values
  79  
  80  
  81  def _remove_given_params(arguments, param_names):
  82      count = 0
  83      used_keys = set()
  84      for key, _ in arguments.unpack():
  85          if key is None:
  86              count += 1
  87          else:
  88              used_keys.add(key)
  89  
  90      for p in param_names:
  91          if count and p.maybe_positional_argument():
  92              count -= 1
  93              continue
  94          if p.string_name in used_keys and p.maybe_keyword_argument():
  95              continue
  96          yield p
  97  
  98  
  99  @to_list
 100  def process_params(param_names, star_count=3):  # default means both * and **
 101      if param_names:
 102          if is_big_annoying_library(param_names[0].parent_context):
 103              # At first this feature can look innocent, but it does a lot of
 104              # type inference in some cases, so we just ditch it.
 105              yield from param_names
 106              return
 107  
 108      used_names = set()
 109      arg_callables = []
 110      kwarg_callables = []
 111  
 112      kw_only_names = []
 113      kwarg_names = []
 114      arg_names = []
 115      original_arg_name = None
 116      original_kwarg_name = None
 117      for p in param_names:
 118          kind = p.get_kind()
 119          if kind == Parameter.VAR_POSITIONAL:
 120              if star_count & 1:
 121                  arg_callables = _iter_nodes_for_param(p)
 122                  original_arg_name = p
 123          elif p.get_kind() == Parameter.VAR_KEYWORD:
 124              if star_count & 2:
 125                  kwarg_callables = list(_iter_nodes_for_param(p))
 126                  original_kwarg_name = p
 127          elif kind == Parameter.KEYWORD_ONLY:
 128              if star_count & 2:
 129                  kw_only_names.append(p)
 130          elif kind == Parameter.POSITIONAL_ONLY:
 131              if star_count & 1:
 132                  yield p
 133          else:
 134              if star_count == 1:
 135                  yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
 136              elif star_count == 2:
 137                  kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
 138              else:
 139                  used_names.add(p.string_name)
 140                  yield p
 141  
 142      # First process *args
 143      longest_param_names = ()
 144      found_arg_signature = False
 145      found_kwarg_signature = False
 146      for func_and_argument in arg_callables:
 147          func, arguments = func_and_argument
 148          new_star_count = star_count
 149          if func_and_argument in kwarg_callables:
 150              kwarg_callables.remove(func_and_argument)
 151          else:
 152              new_star_count = 1
 153  
 154          for signature in func.get_signatures():
 155              found_arg_signature = True
 156              if new_star_count == 3:
 157                  found_kwarg_signature = True
 158              args_for_this_func = []
 159              for p in process_params(
 160                      list(_remove_given_params(
 161                          arguments,
 162                          signature.get_param_names(resolve_stars=False)
 163                      )), new_star_count):
 164                  if p.get_kind() == Parameter.VAR_KEYWORD:
 165                      kwarg_names.append(p)
 166                  elif p.get_kind() == Parameter.VAR_POSITIONAL:
 167                      arg_names.append(p)
 168                  elif p.get_kind() == Parameter.KEYWORD_ONLY:
 169                      kw_only_names.append(p)
 170                  else:
 171                      args_for_this_func.append(p)
 172              if len(args_for_this_func) > len(longest_param_names):
 173                  longest_param_names = args_for_this_func
 174  
 175      for p in longest_param_names:
 176          if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
 177              yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
 178          else:
 179              if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
 180                  used_names.add(p.string_name)
 181              yield p
 182  
 183      if not found_arg_signature and original_arg_name is not None:
 184          yield original_arg_name
 185      elif arg_names:
 186          yield arg_names[0]
 187  
 188      # Then process **kwargs
 189      for func, arguments in kwarg_callables:
 190          for signature in func.get_signatures():
 191              found_kwarg_signature = True
 192              for p in process_params(
 193                      list(_remove_given_params(
 194                          arguments,
 195                          signature.get_param_names(resolve_stars=False)
 196                      )), star_count=2):
 197                  if p.get_kind() == Parameter.VAR_KEYWORD:
 198                      kwarg_names.append(p)
 199                  elif p.get_kind() == Parameter.KEYWORD_ONLY:
 200                      kw_only_names.append(p)
 201  
 202      for p in kw_only_names:
 203          if p.string_name in used_names:
 204              continue
 205          yield p
 206          used_names.add(p.string_name)
 207  
 208      if not found_kwarg_signature and original_kwarg_name is not None:
 209          yield original_kwarg_name
 210      elif kwarg_names:
 211          yield kwarg_names[0]
 212  
 213  
 214  class ParamNameFixedKind(ParamNameWrapper):
 215      def __init__(self, param_name, new_kind):
 216          super().__init__(param_name)
 217          self._new_kind = new_kind
 218  
 219      def get_kind(self):
 220          return self._new_kind
 221