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