Format: text/x-python
⬇ Download File (11.9 KB)
   1  import re
   2  from itertools import zip_longest
   3  
   4  from parso.python import tree
   5  
   6  from jedi import debug
   7  from jedi.inference.utils import PushBackIterator
   8  from jedi.inference import analysis
   9  from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
  10      LazyTreeValue, get_merged_lazy_value
  11  from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
  12  from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
  13  from jedi.inference.value import iterable
  14  from jedi.inference.cache import inference_state_as_method_param_cache
  15  
  16  
  17  def try_iter_content(types, depth=0):
  18      """Helper method for static analysis."""
  19      if depth > 10:
  20          # It's possible that a loop has references on itself (especially with
  21          # CompiledValue). Therefore don't loop infinitely.
  22          return
  23  
  24      for typ in types:
  25          try:
  26              f = typ.py__iter__
  27          except AttributeError:
  28              pass
  29          else:
  30              for lazy_value in f():
  31                  try_iter_content(lazy_value.infer(), depth + 1)
  32  
  33  
  34  class ParamIssue(Exception):
  35      pass
  36  
  37  
  38  def repack_with_argument_clinic(clinic_string):
  39      """
  40      Transforms a function or method with arguments to the signature that is
  41      given as an argument clinic notation.
  42  
  43      Argument clinic is part of CPython and used for all the functions that are
  44      implemented in C (Python 3.7):
  45  
  46          str.split.__text_signature__
  47          # Results in: '($self, /, sep=None, maxsplit=-1)'
  48      """
  49      def decorator(func):
  50          def wrapper(value, arguments):
  51              try:
  52                  args = tuple(iterate_argument_clinic(
  53                      value.inference_state,
  54                      arguments,
  55                      clinic_string,
  56                  ))
  57              except ParamIssue:
  58                  return NO_VALUES
  59              else:
  60                  return func(value, *args)
  61  
  62          return wrapper
  63      return decorator
  64  
  65  
  66  def iterate_argument_clinic(inference_state, arguments, clinic_string):
  67      """Uses a list with argument clinic information (see PEP 436)."""
  68      clinic_args = list(_parse_argument_clinic(clinic_string))
  69  
  70      iterator = PushBackIterator(arguments.unpack())
  71      for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
  72          if stars == 1:
  73              lazy_values = []
  74              for key, argument in iterator:
  75                  if key is not None:
  76                      iterator.push_back((key, argument))
  77                      break
  78  
  79                  lazy_values.append(argument)
  80              yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
  81              lazy_values
  82              continue
  83          elif stars == 2:
  84              raise NotImplementedError()
  85          key, argument = next(iterator, (None, None))
  86          if key is not None:
  87              debug.warning('Keyword arguments in argument clinic are currently not supported.')
  88              raise ParamIssue
  89          if argument is None and not optional:
  90              debug.warning('TypeError: %s expected at least %s arguments, got %s',
  91                            name, len(clinic_args), i)
  92              raise ParamIssue
  93  
  94          value_set = NO_VALUES if argument is None else argument.infer()
  95  
  96          if not value_set and not optional:
  97              # For the stdlib we always want values. If we don't get them,
  98              # that's ok, maybe something is too hard to resolve, however,
  99              # we will not proceed with the type inference of that function.
 100              debug.warning('argument_clinic "%s" not resolvable.', name)
 101              raise ParamIssue
 102          yield value_set
 103  
 104  
 105  def _parse_argument_clinic(string):
 106      allow_kwargs = False
 107      optional = False
 108      while string:
 109          # Optional arguments have to begin with a bracket. And should always be
 110          # at the end of the arguments. This is therefore not a proper argument
 111          # clinic implementation. `range()` for exmple allows an optional start
 112          # value at the beginning.
 113          match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
 114          string = string[len(match.group(0)):]
 115          if not match.group(2):  # A slash -> allow named arguments
 116              allow_kwargs = True
 117              continue
 118          optional = optional or bool(match.group(1))
 119          word = match.group(2)
 120          stars = word.count('*')
 121          word = word[stars:]
 122          yield (word, optional, allow_kwargs, stars)
 123          if stars:
 124              allow_kwargs = True
 125  
 126  
 127  class _AbstractArgumentsMixin:
 128      def unpack(self, funcdef=None):
 129          raise NotImplementedError
 130  
 131      def get_calling_nodes(self):
 132          return []
 133  
 134  
 135  class AbstractArguments(_AbstractArgumentsMixin):
 136      context = None
 137      argument_node = None
 138      trailer = None
 139  
 140  
 141  def unpack_arglist(arglist):
 142      if arglist is None:
 143          return
 144  
 145      if arglist.type != 'arglist' and not (
 146              arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
 147          yield 0, arglist
 148          return
 149  
 150      iterator = iter(arglist.children)
 151      for child in iterator:
 152          if child == ',':
 153              continue
 154          elif child in ('*', '**'):
 155              c = next(iterator, None)
 156              assert c is not None
 157              yield len(child.value), c
 158          elif child.type == 'argument' and \
 159                  child.children[0] in ('*', '**'):
 160              assert len(child.children) == 2
 161              yield len(child.children[0].value), child.children[1]
 162          else:
 163              yield 0, child
 164  
 165  
 166  class TreeArguments(AbstractArguments):
 167      def __init__(self, inference_state, context, argument_node, trailer=None):
 168          """
 169          :param argument_node: May be an argument_node or a list of nodes.
 170          """
 171          self.argument_node = argument_node
 172          self.context = context
 173          self._inference_state = inference_state
 174          self.trailer = trailer  # Can be None, e.g. in a class definition.
 175  
 176      @classmethod
 177      @inference_state_as_method_param_cache()
 178      def create_cached(cls, *args, **kwargs):
 179          return cls(*args, **kwargs)
 180  
 181      def unpack(self, funcdef=None):
 182          named_args = []
 183          for star_count, el in unpack_arglist(self.argument_node):
 184              if star_count == 1:
 185                  arrays = self.context.infer_node(el)
 186                  iterators = [_iterate_star_args(self.context, a, el, funcdef)
 187                               for a in arrays]
 188                  for values in list(zip_longest(*iterators)):
 189                      yield None, get_merged_lazy_value(
 190                          [v for v in values if v is not None]
 191                      )
 192              elif star_count == 2:
 193                  arrays = self.context.infer_node(el)
 194                  for dct in arrays:
 195                      yield from _star_star_dict(self.context, dct, el, funcdef)
 196              else:
 197                  if el.type == 'argument':
 198                      c = el.children
 199                      if len(c) == 3:  # Keyword argument.
 200                          named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
 201                      else:  # Generator comprehension.
 202                          # Include the brackets with the parent.
 203                          sync_comp_for = el.children[1]
 204                          if sync_comp_for.type == 'comp_for':
 205                              sync_comp_for = sync_comp_for.children[1]
 206                          comp = iterable.GeneratorComprehension(
 207                              self._inference_state,
 208                              defining_context=self.context,
 209                              sync_comp_for_node=sync_comp_for,
 210                              entry_node=el.children[0],
 211                          )
 212                          yield None, LazyKnownValue(comp)
 213                  else:
 214                      yield None, LazyTreeValue(self.context, el)
 215  
 216          # Reordering arguments is necessary, because star args sometimes appear
 217          # after named argument, but in the actual order it's prepended.
 218          yield from named_args
 219  
 220      def _as_tree_tuple_objects(self):
 221          for star_count, argument in unpack_arglist(self.argument_node):
 222              default = None
 223              if argument.type == 'argument':
 224                  if len(argument.children) == 3:  # Keyword argument.
 225                      argument, default = argument.children[::2]
 226              yield argument, default, star_count
 227  
 228      def iter_calling_names_with_star(self):
 229          for name, default, star_count in self._as_tree_tuple_objects():
 230              # TODO this function is a bit strange. probably refactor?
 231              if not star_count or not isinstance(name, tree.Name):
 232                  continue
 233  
 234              yield TreeNameDefinition(self.context, name)
 235  
 236      def __repr__(self):
 237          return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
 238  
 239      def get_calling_nodes(self):
 240          old_arguments_list = []
 241          arguments = self
 242  
 243          while arguments not in old_arguments_list:
 244              if not isinstance(arguments, TreeArguments):
 245                  break
 246  
 247              old_arguments_list.append(arguments)
 248              for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
 249                  names = calling_name.goto()
 250                  if len(names) != 1:
 251                      break
 252                  if isinstance(names[0], AnonymousParamName):
 253                      # Dynamic parameters should not have calling nodes, because
 254                      # they are dynamic and extremely random.
 255                      return []
 256                  if not isinstance(names[0], ParamName):
 257                      break
 258                  executed_param_name = names[0].get_executed_param_name()
 259                  arguments = executed_param_name.arguments
 260                  break
 261  
 262          if arguments.argument_node is not None:
 263              return [ContextualizedNode(arguments.context, arguments.argument_node)]
 264          if arguments.trailer is not None:
 265              return [ContextualizedNode(arguments.context, arguments.trailer)]
 266          return []
 267  
 268  
 269  class ValuesArguments(AbstractArguments):
 270      def __init__(self, values_list):
 271          self._values_list = values_list
 272  
 273      def unpack(self, funcdef=None):
 274          for values in self._values_list:
 275              yield None, LazyKnownValues(values)
 276  
 277      def __repr__(self):
 278          return '<%s: %s>' % (self.__class__.__name__, self._values_list)
 279  
 280  
 281  class TreeArgumentsWrapper(_AbstractArgumentsMixin):
 282      def __init__(self, arguments):
 283          self._wrapped_arguments = arguments
 284  
 285      @property
 286      def context(self):
 287          return self._wrapped_arguments.context
 288  
 289      @property
 290      def argument_node(self):
 291          return self._wrapped_arguments.argument_node
 292  
 293      @property
 294      def trailer(self):
 295          return self._wrapped_arguments.trailer
 296  
 297      def unpack(self, func=None):
 298          raise NotImplementedError
 299  
 300      def get_calling_nodes(self):
 301          return self._wrapped_arguments.get_calling_nodes()
 302  
 303      def __repr__(self):
 304          return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)
 305  
 306  
 307  def _iterate_star_args(context, array, input_node, funcdef=None):
 308      if not array.py__getattribute__('__iter__'):
 309          if funcdef is not None:
 310              # TODO this funcdef should not be needed.
 311              m = "TypeError: %s() argument after * must be a sequence, not %s" \
 312                  % (funcdef.name.value, array)
 313              analysis.add(context, 'type-error-star', input_node, message=m)
 314      try:
 315          iter_ = array.py__iter__
 316      except AttributeError:
 317          pass
 318      else:
 319          yield from iter_()
 320  
 321  
 322  def _star_star_dict(context, array, input_node, funcdef):
 323      from jedi.inference.value.instance import CompiledInstance
 324      if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
 325          # For now ignore this case. In the future add proper iterators and just
 326          # make one call without crazy isinstance checks.
 327          return {}
 328      elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
 329          return array.exact_key_items()
 330      else:
 331          if funcdef is not None:
 332              m = "TypeError: %s argument after ** must be a mapping, not %s" \
 333                  % (funcdef.name.value, array)
 334              analysis.add(context, 'type-error-star-star', input_node, message=m)
 335          return {}
 336