Format: text/x-python
⬇ Download File (7.4 KB)
   1  """
   2  A module to deal with stuff like `list.append` and `set.add`.
   3  
   4  Array modifications
   5  *******************
   6  
   7  If the content of an array (``set``/``list``) is requested somewhere, the
   8  current module will be checked for appearances of ``arr.append``,
   9  ``arr.insert``, etc.  If the ``arr`` name points to an actual array, the
  10  content will be added
  11  
  12  This can be really cpu intensive, as you can imagine. Because |jedi| has to
  13  follow **every** ``append`` and check whether it's the right array. However this
  14  works pretty good, because in *slow* cases, the recursion detector and other
  15  settings will stop this process.
  16  
  17  It is important to note that:
  18  
  19  1. Array modifications work only in the current module.
  20  2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
  21  """
  22  from jedi import debug
  23  from jedi import settings
  24  from jedi.inference import recursion
  25  from jedi.inference.base_value import ValueSet, NO_VALUES, HelperValueMixin, \
  26      ValueWrapper
  27  from jedi.inference.lazy_value import LazyKnownValues
  28  from jedi.inference.helpers import infer_call_of_leaf
  29  from jedi.inference.cache import inference_state_method_cache
  30  
  31  _sentinel = object()
  32  
  33  
  34  def check_array_additions(context, sequence):
  35      """ Just a mapper function for the internal _internal_check_array_additions """
  36      if sequence.array_type not in ('list', 'set'):
  37          # TODO also check for dict updates
  38          return NO_VALUES
  39  
  40      return _internal_check_array_additions(context, sequence)
  41  
  42  
  43  @inference_state_method_cache(default=NO_VALUES)
  44  @debug.increase_indent
  45  def _internal_check_array_additions(context, sequence):
  46      """
  47      Checks if a `Array` has "add" (append, insert, extend) statements:
  48  
  49      >>> a = [""]
  50      >>> a.append(1)
  51      """
  52      from jedi.inference import arguments
  53  
  54      debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
  55      module_context = context.get_root_context()
  56      if not settings.dynamic_array_additions or module_context.is_compiled():
  57          debug.dbg('Dynamic array search aborted.', color='MAGENTA')
  58          return NO_VALUES
  59  
  60      def find_additions(context, arglist, add_name):
  61          params = list(arguments.TreeArguments(context.inference_state, context, arglist).unpack())
  62          result = set()
  63          if add_name in ['insert']:
  64              params = params[1:]
  65          if add_name in ['append', 'add', 'insert']:
  66              for key, lazy_value in params:
  67                  result.add(lazy_value)
  68          elif add_name in ['extend', 'update']:
  69              for key, lazy_value in params:
  70                  result |= set(lazy_value.infer().iterate())
  71          return result
  72  
  73      temp_param_add, settings.dynamic_params_for_other_modules = \
  74          settings.dynamic_params_for_other_modules, False
  75  
  76      is_list = sequence.name.string_name == 'list'
  77      search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
  78  
  79      added_types = set()
  80      for add_name in search_names:
  81          try:
  82              possible_names = module_context.tree_node.get_used_names()[add_name]
  83          except KeyError:
  84              continue
  85          else:
  86              for name in possible_names:
  87                  value_node = context.tree_node
  88                  if not (value_node.start_pos < name.start_pos < value_node.end_pos):
  89                      continue
  90                  trailer = name.parent
  91                  power = trailer.parent
  92                  trailer_pos = power.children.index(trailer)
  93                  try:
  94                      execution_trailer = power.children[trailer_pos + 1]
  95                  except IndexError:
  96                      continue
  97                  else:
  98                      if execution_trailer.type != 'trailer' \
  99                              or execution_trailer.children[0] != '(' \
 100                              or execution_trailer.children[1] == ')':
 101                          continue
 102  
 103                  random_context = context.create_context(name)
 104  
 105                  with recursion.execution_allowed(context.inference_state, power) as allowed:
 106                      if allowed:
 107                          found = infer_call_of_leaf(
 108                              random_context,
 109                              name,
 110                              cut_own_trailer=True
 111                          )
 112                          if sequence in found:
 113                              # The arrays match. Now add the results
 114                              added_types |= find_additions(
 115                                  random_context,
 116                                  execution_trailer.children[1],
 117                                  add_name
 118                              )
 119  
 120      # reset settings
 121      settings.dynamic_params_for_other_modules = temp_param_add
 122      debug.dbg('Dynamic array result %s', added_types, color='MAGENTA')
 123      return added_types
 124  
 125  
 126  def get_dynamic_array_instance(instance, arguments):
 127      """Used for set() and list() instances."""
 128      ai = _DynamicArrayAdditions(instance, arguments)
 129      from jedi.inference import arguments
 130      return arguments.ValuesArguments([ValueSet([ai])])
 131  
 132  
 133  class _DynamicArrayAdditions(HelperValueMixin):
 134      """
 135      Used for the usage of set() and list().
 136      This is definitely a hack, but a good one :-)
 137      It makes it possible to use set/list conversions.
 138  
 139      This is not a proper context, because it doesn't have to be. It's not used
 140      in the wild, it's just used within typeshed as an argument to `__init__`
 141      for set/list and never used in any other place.
 142      """
 143      def __init__(self, instance, arguments):
 144          self._instance = instance
 145          self._arguments = arguments
 146  
 147      def py__class__(self):
 148          tuple_, = self._instance.inference_state.builtins_module.py__getattribute__('tuple')
 149          return tuple_
 150  
 151      def py__iter__(self, contextualized_node=None):
 152          arguments = self._arguments
 153          try:
 154              _, lazy_value = next(arguments.unpack())
 155          except StopIteration:
 156              pass
 157          else:
 158              yield from lazy_value.infer().iterate()
 159  
 160          from jedi.inference.arguments import TreeArguments
 161          if isinstance(arguments, TreeArguments):
 162              additions = _internal_check_array_additions(arguments.context, self._instance)
 163              yield from additions
 164  
 165      def iterate(self, contextualized_node=None, is_async=False):
 166          return self.py__iter__(contextualized_node)
 167  
 168  
 169  class _Modification(ValueWrapper):
 170      def __init__(self, wrapped_value, assigned_values, contextualized_key):
 171          super().__init__(wrapped_value)
 172          self._assigned_values = assigned_values
 173          self._contextualized_key = contextualized_key
 174  
 175      def py__getitem__(self, *args, **kwargs):
 176          return self._wrapped_value.py__getitem__(*args, **kwargs) | self._assigned_values
 177  
 178      def py__simple_getitem__(self, index):
 179          actual = [
 180              v.get_safe_value(_sentinel)
 181              for v in self._contextualized_key.infer()
 182          ]
 183          if index in actual:
 184              return self._assigned_values
 185          return self._wrapped_value.py__simple_getitem__(index)
 186  
 187  
 188  class DictModification(_Modification):
 189      def py__iter__(self, contextualized_node=None):
 190          yield from self._wrapped_value.py__iter__(contextualized_node)
 191          yield self._contextualized_key
 192  
 193      def get_key_values(self):
 194          return self._wrapped_value.get_key_values() | self._contextualized_key.infer()
 195  
 196  
 197  class ListModification(_Modification):
 198      def py__iter__(self, contextualized_node=None):
 199          yield from self._wrapped_value.py__iter__(contextualized_node)
 200          yield LazyKnownValues(self._assigned_values)
 201