dynamic_arrays.py 7.4 KB

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