Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/value/dynamic_arrays.py: 25%
110 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2A module to deal with stuff like `list.append` and `set.add`.
4Array modifications
5*******************
7If the content of an array (``set``/``list``) is requested somewhere, the
8current module will be checked for appearances of ``arr.append``,
9``arr.insert``, etc. If the ``arr`` name points to an actual array, the
10content will be added
12This can be really cpu intensive, as you can imagine. Because |jedi| has to
13follow **every** ``append`` and check whether it's the right array. However this
14works pretty good, because in *slow* cases, the recursion detector and other
15settings will stop this process.
17It is important to note that:
191. Array modifications work only in the current module.
202. Jedi only checks Array additions; ``list.pop``, etc are ignored.
21"""
22from jedi import debug
23from jedi import settings
24from jedi.inference import recursion
25from jedi.inference.base_value import ValueSet, NO_VALUES, HelperValueMixin, \
26 ValueWrapper
27from jedi.inference.lazy_value import LazyKnownValues
28from jedi.inference.helpers import infer_call_of_leaf
29from jedi.inference.cache import inference_state_method_cache
31_sentinel = object()
34def 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
40 return _internal_check_array_additions(context, sequence)
43@inference_state_method_cache(default=NO_VALUES)
44@debug.increase_indent
45def _internal_check_array_additions(context, sequence):
46 """
47 Checks if a `Array` has "add" (append, insert, extend) statements:
49 >>> a = [""]
50 >>> a.append(1)
51 """
52 from jedi.inference import arguments
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
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
73 temp_param_add, settings.dynamic_params_for_other_modules = \
74 settings.dynamic_params_for_other_modules, False
76 is_list = sequence.name.string_name == 'list'
77 search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
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
103 random_context = context.create_context(name)
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 )
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
126def 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])])
133class _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.
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
147 def py__class__(self):
148 tuple_, = self._instance.inference_state.builtins_module.py__getattribute__('tuple')
149 return tuple_
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()
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
165 def iterate(self, contextualized_node=None, is_async=False):
166 return self.py__iter__(contextualized_node)
169class _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
175 def py__getitem__(self, *args, **kwargs):
176 return self._wrapped_value.py__getitem__(*args, **kwargs) | self._assigned_values
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)
188class 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
193 def get_key_values(self):
194 return self._wrapped_value.get_key_values() | self._contextualized_key.infer()
197class 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)