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

1""" 

2A module to deal with stuff like `list.append` and `set.add`. 

3 

4Array modifications 

5******************* 

6 

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 

11 

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. 

16 

17It is important to note that: 

18 

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 

30 

31_sentinel = object() 

32 

33 

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 

39 

40 return _internal_check_array_additions(context, sequence) 

41 

42 

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: 

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 

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])]) 

131 

132 

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. 

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 

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 

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 

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 

192 

193 def get_key_values(self): 

194 return self._wrapped_value.get_key_values() | self._contextualized_key.infer() 

195 

196 

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)