1import re
2from itertools import zip_longest
3from typing import Any
4
5from parso.python import tree
6
7from jedi import debug
8from jedi.inference.utils import PushBackIterator
9from jedi.inference import analysis
10from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
11 LazyTreeValue, get_merged_lazy_value
12from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
13from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
14from jedi.inference.value import iterable
15from jedi.inference.cache import inference_state_as_method_param_cache
16
17
18def try_iter_content(types, depth=0):
19 """Helper method for static analysis."""
20 if depth > 10:
21 # It's possible that a loop has references on itself (especially with
22 # CompiledValue). Therefore don't loop infinitely.
23 return
24
25 for typ in types:
26 try:
27 f = typ.py__iter__
28 except AttributeError:
29 pass
30 else:
31 for lazy_value in f():
32 try_iter_content(lazy_value.infer(), depth + 1)
33
34
35class ParamIssue(Exception):
36 pass
37
38
39def repack_with_argument_clinic(clinic_string):
40 """
41 Transforms a function or method with arguments to the signature that is
42 given as an argument clinic notation.
43
44 Argument clinic is part of CPython and used for all the functions that are
45 implemented in C (Python 3.7):
46
47 str.split.__text_signature__
48 # Results in: '($self, /, sep=None, maxsplit=-1)'
49 """
50 def decorator(func):
51 def wrapper(value, arguments):
52 try:
53 args = tuple(iterate_argument_clinic(
54 value.inference_state,
55 arguments,
56 clinic_string,
57 ))
58 except ParamIssue:
59 return NO_VALUES
60 else:
61 return func(value, *args)
62
63 return wrapper
64 return decorator
65
66
67def iterate_argument_clinic(inference_state, arguments, clinic_string):
68 """Uses a list with argument clinic information (see PEP 436)."""
69 clinic_args = list(_parse_argument_clinic(clinic_string))
70
71 iterator = PushBackIterator(arguments.unpack())
72 for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
73 if stars == 1:
74 lazy_values = []
75 for key, argument in iterator:
76 if key is not None:
77 iterator.push_back((key, argument))
78 break
79
80 lazy_values.append(argument)
81 yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
82 lazy_values
83 continue
84 elif stars == 2:
85 raise NotImplementedError()
86 key, argument = next(iterator, (None, None))
87 if key is not None:
88 debug.warning('Keyword arguments in argument clinic are currently not supported.')
89 raise ParamIssue
90 if argument is None and not optional:
91 debug.warning('TypeError: %s expected at least %s arguments, got %s',
92 name, len(clinic_args), i)
93 raise ParamIssue
94
95 value_set = NO_VALUES if argument is None else argument.infer()
96
97 if not value_set and not optional:
98 # For the stdlib we always want values. If we don't get them,
99 # that's ok, maybe something is too hard to resolve, however,
100 # we will not proceed with the type inference of that function.
101 debug.warning('argument_clinic "%s" not resolvable.', name)
102 raise ParamIssue
103 yield value_set
104
105
106def _parse_argument_clinic(string):
107 allow_kwargs = False
108 optional = False
109 while string:
110 # Optional arguments have to begin with a bracket. And should always be
111 # at the end of the arguments. This is therefore not a proper argument
112 # clinic implementation. `range()` for exmple allows an optional start
113 # value at the beginning.
114 match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
115 string = string[len(match.group(0)):]
116 if not match.group(2): # A slash -> allow named arguments
117 allow_kwargs = True
118 continue
119 optional = optional or bool(match.group(1))
120 word = match.group(2)
121 stars = word.count('*')
122 word = word[stars:]
123 yield (word, optional, allow_kwargs, stars)
124 if stars:
125 allow_kwargs = True
126
127
128class _AbstractArgumentsMixin:
129 def unpack(self, funcdef=None):
130 raise NotImplementedError
131
132 def get_calling_nodes(self):
133 return []
134
135
136class AbstractArguments(_AbstractArgumentsMixin):
137 context = None
138 argument_node: Any = None
139 trailer = None
140
141
142def unpack_arglist(arglist):
143 if arglist is None:
144 return
145
146 if arglist.type != 'arglist' and not (
147 arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
148 yield 0, arglist
149 return
150
151 iterator = iter(arglist.children)
152 for child in iterator:
153 if child == ',':
154 continue
155 elif child in ('*', '**'):
156 c = next(iterator, None)
157 assert c is not None
158 yield len(child.value), c
159 elif child.type == 'argument' and \
160 child.children[0] in ('*', '**'):
161 assert len(child.children) == 2
162 yield len(child.children[0].value), child.children[1]
163 else:
164 yield 0, child
165
166
167class TreeArguments(AbstractArguments):
168 context: Any
169
170 def __init__(self, inference_state, context, argument_node, trailer=None):
171 """
172 :param argument_node: May be an argument_node or a list of nodes.
173 """
174 self.argument_node = argument_node
175 self.context = context
176 self._inference_state = inference_state
177 self.trailer = trailer # Can be None, e.g. in a class definition.
178
179 @classmethod
180 @inference_state_as_method_param_cache()
181 def create_cached(cls, *args, **kwargs):
182 return cls(*args, **kwargs)
183
184 def unpack(self, funcdef=None):
185 named_args = []
186 for star_count, el in unpack_arglist(self.argument_node):
187 if star_count == 1:
188 arrays = self.context.infer_node(el)
189 iterators = [_iterate_star_args(self.context, a, el, funcdef)
190 for a in arrays]
191 for values in list(zip_longest(*iterators)):
192 yield None, get_merged_lazy_value(
193 [v for v in values if v is not None]
194 )
195 elif star_count == 2:
196 arrays = self.context.infer_node(el)
197 for dct in arrays:
198 yield from _star_star_dict(self.context, dct, el, funcdef)
199 else:
200 if el.type == 'argument':
201 c = el.children
202 if len(c) == 3: # Keyword argument.
203 named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
204 else: # Generator comprehension.
205 # Include the brackets with the parent.
206 sync_comp_for = el.children[1]
207 if sync_comp_for.type == 'comp_for':
208 sync_comp_for = sync_comp_for.children[1]
209 comp = iterable.GeneratorComprehension(
210 self._inference_state,
211 defining_context=self.context,
212 sync_comp_for_node=sync_comp_for,
213 entry_node=el.children[0],
214 )
215 yield None, LazyKnownValue(comp)
216 else:
217 yield None, LazyTreeValue(self.context, el)
218
219 # Reordering arguments is necessary, because star args sometimes appear
220 # after named argument, but in the actual order it's prepended.
221 yield from named_args
222
223 def _as_tree_tuple_objects(self):
224 for star_count, argument in unpack_arglist(self.argument_node):
225 default = None
226 if argument.type == 'argument':
227 if len(argument.children) == 3: # Keyword argument.
228 argument, default = argument.children[::2]
229 yield argument, default, star_count
230
231 def iter_calling_names_with_star(self):
232 for name, default, star_count in self._as_tree_tuple_objects():
233 # TODO this function is a bit strange. probably refactor?
234 if not star_count or not isinstance(name, tree.Name):
235 continue
236
237 yield TreeNameDefinition(self.context, name)
238
239 def __repr__(self):
240 return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
241
242 def get_calling_nodes(self):
243 old_arguments_list = []
244 arguments = self
245
246 while arguments not in old_arguments_list:
247 if not isinstance(arguments, TreeArguments):
248 break
249
250 old_arguments_list.append(arguments)
251 for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
252 names = calling_name.goto()
253 if len(names) != 1:
254 break
255 if isinstance(names[0], AnonymousParamName):
256 # Dynamic parameters should not have calling nodes, because
257 # they are dynamic and extremely random.
258 return []
259 if not isinstance(names[0], ParamName):
260 break
261 executed_param_name = names[0].get_executed_param_name()
262 arguments = executed_param_name.arguments
263 break
264
265 if arguments.argument_node is not None:
266 return [ContextualizedNode(arguments.context, arguments.argument_node)]
267 if arguments.trailer is not None:
268 return [ContextualizedNode(arguments.context, arguments.trailer)]
269 return []
270
271
272class ValuesArguments(AbstractArguments):
273 def __init__(self, values_list):
274 self._values_list = values_list
275
276 def unpack(self, funcdef=None):
277 for values in self._values_list:
278 yield None, LazyKnownValues(values)
279
280 def __repr__(self):
281 return '<%s: %s>' % (self.__class__.__name__, self._values_list)
282
283
284class TreeArgumentsWrapper(_AbstractArgumentsMixin):
285 def __init__(self, arguments):
286 self._wrapped_arguments = arguments
287
288 @property
289 def context(self):
290 return self._wrapped_arguments.context
291
292 @property
293 def argument_node(self):
294 return self._wrapped_arguments.argument_node
295
296 @property
297 def trailer(self):
298 return self._wrapped_arguments.trailer
299
300 def unpack(self, func=None):
301 raise NotImplementedError
302
303 def get_calling_nodes(self):
304 return self._wrapped_arguments.get_calling_nodes()
305
306 def __repr__(self):
307 return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)
308
309
310def _iterate_star_args(context, array, input_node, funcdef=None):
311 if not array.py__getattribute__('__iter__'):
312 if funcdef is not None:
313 # TODO this funcdef should not be needed.
314 m = "TypeError: %s() argument after * must be a sequence, not %s" \
315 % (funcdef.name.value, array)
316 analysis.add(context, 'type-error-star', input_node, message=m)
317 try:
318 iter_ = array.py__iter__
319 except AttributeError:
320 pass
321 else:
322 yield from iter_()
323
324
325def _star_star_dict(context, array, input_node, funcdef):
326 from jedi.inference.value.instance import CompiledInstance
327 if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
328 # For now ignore this case. In the future add proper iterators and just
329 # make one call without crazy isinstance checks.
330 return {}
331 elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
332 return array.exact_key_items()
333 else:
334 if funcdef is not None:
335 m = "TypeError: %s argument after ** must be a mapping, not %s" \
336 % (funcdef.name.value, array)
337 analysis.add(context, 'type-error-star-star', input_node, message=m)
338 return {}