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