Format: text/x-python
⬇ Download File (10.2 KB)
   1  from collections import defaultdict
   2  from inspect import Parameter
   3  
   4  from jedi import debug
   5  from jedi.inference.utils import PushBackIterator
   6  from jedi.inference import analysis
   7  from jedi.inference.lazy_value import LazyKnownValue, \
   8      LazyTreeValue, LazyUnknownValue
   9  from jedi.inference.value import iterable
  10  from jedi.inference.names import ParamName
  11  
  12  
  13  def _add_argument_issue(error_name, lazy_value, message):
  14      if isinstance(lazy_value, LazyTreeValue):
  15          node = lazy_value.data
  16          if node.parent.type == 'argument':
  17              node = node.parent
  18          return analysis.add(lazy_value.context, error_name, node, message)
  19  
  20  
  21  class ExecutedParamName(ParamName):
  22      def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
  23          super().__init__(function_value, param_node.name, arguments=arguments)
  24          self._lazy_value = lazy_value
  25          self._is_default = is_default
  26  
  27      def infer(self):
  28          return self._lazy_value.infer()
  29  
  30      def matches_signature(self):
  31          if self._is_default:
  32              return True
  33          argument_values = self.infer().py__class__()
  34          if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
  35              return True
  36          annotations = self.infer_annotation(execute_annotation=False)
  37          if not annotations:
  38              # If we cannot infer annotations - or there aren't any - pretend
  39              # that the signature matches.
  40              return True
  41          matches = any(c1.is_sub_class_of(c2)
  42                        for c1 in argument_values
  43                        for c2 in annotations.gather_annotation_classes())
  44          debug.dbg("param compare %s: %s <=> %s",
  45                    matches, argument_values, annotations, color='BLUE')
  46          return matches
  47  
  48      def __repr__(self):
  49          return '<%s: %s>' % (self.__class__.__name__, self.string_name)
  50  
  51  
  52  def get_executed_param_names_and_issues(function_value, arguments):
  53      """
  54      Return a tuple of:
  55        - a list of `ExecutedParamName`s corresponding to the arguments of the
  56          function execution `function_value`, containing the inferred value of
  57          those arguments (whether explicit or default)
  58        - a list of the issues encountered while building that list
  59  
  60      For example, given:
  61      ```
  62      def foo(a, b, c=None, d='d'): ...
  63  
  64      foo(42, c='c')
  65      ```
  66  
  67      Then for the execution of `foo`, this will return a tuple containing:
  68        - a list with entries for each parameter a, b, c & d; the entries for a,
  69          c, & d will have their values (42, 'c' and 'd' respectively) included.
  70        - a list with a single entry about the lack of a value for `b`
  71      """
  72      def too_many_args(argument):
  73          m = _error_argument_count(funcdef, len(unpacked_va))
  74          # Just report an error for the first param that is not needed (like
  75          # cPython).
  76          if arguments.get_calling_nodes():
  77              # There might not be a valid calling node so check for that first.
  78              issues.append(
  79                  _add_argument_issue(
  80                      'type-error-too-many-arguments',
  81                      argument,
  82                      message=m
  83                  )
  84              )
  85          else:
  86              issues.append(None)
  87              debug.warning('non-public warning: %s', m)
  88  
  89      issues = []  # List[Optional[analysis issue]]
  90      result_params = []
  91      param_dict = {}
  92      funcdef = function_value.tree_node
  93      # Default params are part of the value where the function was defined.
  94      # This means that they might have access on class variables that the
  95      # function itself doesn't have.
  96      default_param_context = function_value.get_default_param_context()
  97  
  98      for param in funcdef.get_params():
  99          param_dict[param.name.value] = param
 100      unpacked_va = list(arguments.unpack(funcdef))
 101      var_arg_iterator = PushBackIterator(iter(unpacked_va))
 102  
 103      non_matching_keys = defaultdict(lambda: [])
 104      keys_used = {}
 105      keys_only = False
 106      had_multiple_value_error = False
 107      for param in funcdef.get_params():
 108          # The value and key can both be null. There, the defaults apply.
 109          # args / kwargs will just be empty arrays / dicts, respectively.
 110          # Wrong value count is just ignored. If you try to test cases that are
 111          # not allowed in Python, Jedi will maybe not show any completions.
 112          is_default = False
 113          key, argument = next(var_arg_iterator, (None, None))
 114          while key is not None:
 115              keys_only = True
 116              try:
 117                  key_param = param_dict[key]
 118              except KeyError:
 119                  non_matching_keys[key] = argument
 120              else:
 121                  if key in keys_used:
 122                      had_multiple_value_error = True
 123                      m = ("TypeError: %s() got multiple values for keyword argument '%s'."
 124                           % (funcdef.name, key))
 125                      for contextualized_node in arguments.get_calling_nodes():
 126                          issues.append(
 127                              analysis.add(contextualized_node.context,
 128                                           'type-error-multiple-values',
 129                                           contextualized_node.node, message=m)
 130                          )
 131                  else:
 132                      keys_used[key] = ExecutedParamName(
 133                          function_value, arguments, key_param, argument)
 134              key, argument = next(var_arg_iterator, (None, None))
 135  
 136          try:
 137              result_params.append(keys_used[param.name.value])
 138              continue
 139          except KeyError:
 140              pass
 141  
 142          if param.star_count == 1:
 143              # *args param
 144              lazy_value_list = []
 145              if argument is not None:
 146                  lazy_value_list.append(argument)
 147                  for key, argument in var_arg_iterator:
 148                      # Iterate until a key argument is found.
 149                      if key:
 150                          var_arg_iterator.push_back((key, argument))
 151                          break
 152                      lazy_value_list.append(argument)
 153              seq = iterable.FakeTuple(function_value.inference_state, lazy_value_list)
 154              result_arg = LazyKnownValue(seq)
 155          elif param.star_count == 2:
 156              if argument is not None:
 157                  too_many_args(argument)
 158              # **kwargs param
 159              dct = iterable.FakeDict(function_value.inference_state, dict(non_matching_keys))
 160              result_arg = LazyKnownValue(dct)
 161              non_matching_keys = {}
 162          else:
 163              # normal param
 164              if argument is None:
 165                  # No value: Return an empty container
 166                  if param.default is None:
 167                      result_arg = LazyUnknownValue()
 168                      if not keys_only:
 169                          for contextualized_node in arguments.get_calling_nodes():
 170                              m = _error_argument_count(funcdef, len(unpacked_va))
 171                              issues.append(
 172                                  analysis.add(
 173                                      contextualized_node.context,
 174                                      'type-error-too-few-arguments',
 175                                      contextualized_node.node,
 176                                      message=m,
 177                                  )
 178                              )
 179                  else:
 180                      result_arg = LazyTreeValue(default_param_context, param.default)
 181                      is_default = True
 182              else:
 183                  result_arg = argument
 184  
 185          result_params.append(ExecutedParamName(
 186              function_value, arguments, param, result_arg, is_default=is_default
 187          ))
 188          if not isinstance(result_arg, LazyUnknownValue):
 189              keys_used[param.name.value] = result_params[-1]
 190  
 191      if keys_only:
 192          # All arguments should be handed over to the next function. It's not
 193          # about the values inside, it's about the names. Jedi needs to now that
 194          # there's nothing to find for certain names.
 195          for k in set(param_dict) - set(keys_used):
 196              param = param_dict[k]
 197  
 198              if not (non_matching_keys or had_multiple_value_error
 199                      or param.star_count or param.default):
 200                  # add a warning only if there's not another one.
 201                  for contextualized_node in arguments.get_calling_nodes():
 202                      m = _error_argument_count(funcdef, len(unpacked_va))
 203                      issues.append(
 204                          analysis.add(contextualized_node.context,
 205                                       'type-error-too-few-arguments',
 206                                       contextualized_node.node, message=m)
 207                      )
 208  
 209      for key, lazy_value in non_matching_keys.items():
 210          m = "TypeError: %s() got an unexpected keyword argument '%s'." \
 211              % (funcdef.name, key)
 212          issues.append(
 213              _add_argument_issue(
 214                  'type-error-keyword-argument',
 215                  lazy_value,
 216                  message=m
 217              )
 218          )
 219  
 220      remaining_arguments = list(var_arg_iterator)
 221      if remaining_arguments:
 222          first_key, lazy_value = remaining_arguments[0]
 223          too_many_args(lazy_value)
 224      return result_params, issues
 225  
 226  
 227  def get_executed_param_names(function_value, arguments):
 228      """
 229      Return a list of `ExecutedParamName`s corresponding to the arguments of the
 230      function execution `function_value`, containing the inferred value of those
 231      arguments (whether explicit or default). Any issues building this list (for
 232      example required arguments which are missing in the invocation) are ignored.
 233  
 234      For example, given:
 235      ```
 236      def foo(a, b, c=None, d='d'): ...
 237  
 238      foo(42, c='c')
 239      ```
 240  
 241      Then for the execution of `foo`, this will return a list containing entries
 242      for each parameter a, b, c & d; the entries for a, c, & d will have their
 243      values (42, 'c' and 'd' respectively) included.
 244      """
 245      return get_executed_param_names_and_issues(function_value, arguments)[0]
 246  
 247  
 248  def _error_argument_count(funcdef, actual_count):
 249      params = funcdef.get_params()
 250      default_arguments = sum(1 for p in params if p.default or p.star_count)
 251  
 252      if default_arguments == 0:
 253          before = 'exactly '
 254      else:
 255          before = 'from %s to ' % (len(params) - default_arguments)
 256      return ('TypeError: %s() takes %s%s arguments (%s given).'
 257              % (funcdef.name, before, len(params), actual_count))
 258