]>
Commit | Line | Data |
---|---|---|
1 | import re | |
2 | from textwrap import dedent | |
3 | from inspect import Parameter | |
4 | ||
5 | from parso.python.token import PythonTokenTypes | |
6 | from parso.python import tree | |
7 | from parso.tree import search_ancestor, Leaf | |
8 | from parso import split_lines | |
9 | ||
10 | from jedi import debug | |
11 | from jedi import settings | |
12 | from jedi.api import classes | |
13 | from jedi.api import helpers | |
14 | from jedi.api import keywords | |
15 | from jedi.api.strings import complete_dict | |
16 | from jedi.api.file_name import complete_file_name | |
17 | from jedi.inference import imports | |
18 | from jedi.inference.base_value import ValueSet | |
19 | from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names | |
20 | from jedi.inference.context import get_global_filters | |
21 | from jedi.inference.value import TreeInstance | |
22 | from jedi.inference.docstring_utils import DocstringModule | |
23 | from jedi.inference.names import ParamNameWrapper, SubModuleName | |
24 | from jedi.inference.gradual.conversion import convert_values, convert_names | |
25 | from jedi.parser_utils import cut_value_at_position | |
26 | from jedi.plugins import plugin_manager | |
27 | ||
28 | ||
29 | class ParamNameWithEquals(ParamNameWrapper): | |
30 | def get_public_name(self): | |
31 | return self.string_name + '=' | |
32 | ||
33 | ||
34 | def _get_signature_param_names(signatures, positional_count, used_kwargs): | |
35 | # Add named params | |
36 | for call_sig in signatures: | |
37 | for i, p in enumerate(call_sig.params): | |
38 | kind = p.kind | |
39 | if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD: | |
40 | continue | |
41 | if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \ | |
42 | and p.name not in used_kwargs: | |
43 | yield ParamNameWithEquals(p._name) | |
44 | ||
45 | ||
46 | def _must_be_kwarg(signatures, positional_count, used_kwargs): | |
47 | if used_kwargs: | |
48 | return True | |
49 | ||
50 | must_be_kwarg = True | |
51 | for signature in signatures: | |
52 | for i, p in enumerate(signature.params): | |
53 | kind = p.kind | |
54 | if kind is Parameter.VAR_POSITIONAL: | |
55 | # In case there were not already kwargs, the next param can | |
56 | # always be a normal argument. | |
57 | return False | |
58 | ||
59 | if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD, | |
60 | Parameter.POSITIONAL_ONLY): | |
61 | must_be_kwarg = False | |
62 | break | |
63 | if not must_be_kwarg: | |
64 | break | |
65 | return must_be_kwarg | |
66 | ||
67 | ||
68 | def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name): | |
69 | comp_dct = set() | |
70 | if settings.case_insensitive_completion: | |
71 | like_name = like_name.lower() | |
72 | for name in completion_names: | |
73 | string = name.string_name | |
74 | if settings.case_insensitive_completion: | |
75 | string = string.lower() | |
76 | if helpers.match(string, like_name, fuzzy=fuzzy): | |
77 | new = classes.Completion( | |
78 | inference_state, | |
79 | name, | |
80 | stack, | |
81 | len(like_name), | |
82 | is_fuzzy=fuzzy, | |
83 | cached_name=cached_name, | |
84 | ) | |
85 | k = (new.name, new.complete) # key | |
86 | if k not in comp_dct: | |
87 | comp_dct.add(k) | |
88 | tree_name = name.tree_name | |
89 | if tree_name is not None: | |
90 | definition = tree_name.get_definition() | |
91 | if definition is not None and definition.type == 'del_stmt': | |
92 | continue | |
93 | yield new | |
94 | ||
95 | ||
96 | def _remove_duplicates(completions, other_completions): | |
97 | names = {d.name for d in other_completions} | |
98 | return [c for c in completions if c.name not in names] | |
99 | ||
100 | ||
101 | def get_user_context(module_context, position): | |
102 | """ | |
103 | Returns the scope in which the user resides. This includes flows. | |
104 | """ | |
105 | leaf = module_context.tree_node.get_leaf_for_position(position, include_prefixes=True) | |
106 | return module_context.create_context(leaf) | |
107 | ||
108 | ||
109 | def get_flow_scope_node(module_node, position): | |
110 | node = module_node.get_leaf_for_position(position, include_prefixes=True) | |
111 | while not isinstance(node, (tree.Scope, tree.Flow)): | |
112 | node = node.parent | |
113 | ||
114 | return node | |
115 | ||
116 | ||
117 | @plugin_manager.decorate() | |
118 | def complete_param_names(context, function_name, decorator_nodes): | |
119 | # Basically there's no way to do param completion. The plugins are | |
120 | # responsible for this. | |
121 | return [] | |
122 | ||
123 | ||
124 | class Completion: | |
125 | def __init__(self, inference_state, module_context, code_lines, position, | |
126 | signatures_callback, fuzzy=False): | |
127 | self._inference_state = inference_state | |
128 | self._module_context = module_context | |
129 | self._module_node = module_context.tree_node | |
130 | self._code_lines = code_lines | |
131 | ||
132 | # The first step of completions is to get the name | |
133 | self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position) | |
134 | # The actual cursor position is not what we need to calculate | |
135 | # everything. We want the start of the name we're on. | |
136 | self._original_position = position | |
137 | self._signatures_callback = signatures_callback | |
138 | ||
139 | self._fuzzy = fuzzy | |
140 | ||
141 | def complete(self): | |
142 | leaf = self._module_node.get_leaf_for_position( | |
143 | self._original_position, | |
144 | include_prefixes=True | |
145 | ) | |
146 | string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position) | |
147 | ||
148 | prefixed_completions = complete_dict( | |
149 | self._module_context, | |
150 | self._code_lines, | |
151 | start_leaf or leaf, | |
152 | self._original_position, | |
153 | None if string is None else quote + string, | |
154 | fuzzy=self._fuzzy, | |
155 | ) | |
156 | ||
157 | if string is not None and not prefixed_completions: | |
158 | prefixed_completions = list(complete_file_name( | |
159 | self._inference_state, self._module_context, start_leaf, quote, string, | |
160 | self._like_name, self._signatures_callback, | |
161 | self._code_lines, self._original_position, | |
162 | self._fuzzy | |
163 | )) | |
164 | if string is not None: | |
165 | if not prefixed_completions and '\n' in string: | |
166 | # Complete only multi line strings | |
167 | prefixed_completions = self._complete_in_string(start_leaf, string) | |
168 | return prefixed_completions | |
169 | ||
170 | cached_name, completion_names = self._complete_python(leaf) | |
171 | ||
172 | completions = list(filter_names(self._inference_state, completion_names, | |
173 | self.stack, self._like_name, | |
174 | self._fuzzy, cached_name=cached_name)) | |
175 | ||
176 | return ( | |
177 | # Removing duplicates mostly to remove False/True/None duplicates. | |
178 | _remove_duplicates(prefixed_completions, completions) | |
179 | + sorted(completions, key=lambda x: (x.name.startswith('__'), | |
180 | x.name.startswith('_'), | |
181 | x.name.lower())) | |
182 | ) | |
183 | ||
184 | def _complete_python(self, leaf): | |
185 | """ | |
186 | Analyzes the current context of a completion and decides what to | |
187 | return. | |
188 | ||
189 | Technically this works by generating a parser stack and analysing the | |
190 | current stack for possible grammar nodes. | |
191 | ||
192 | Possible enhancements: | |
193 | - global/nonlocal search global | |
194 | - yield from / raise from <- could be only exceptions/generators | |
195 | - In args: */**: no completion | |
196 | - In params (also lambda): no completion before = | |
197 | """ | |
198 | grammar = self._inference_state.grammar | |
199 | self.stack = stack = None | |
200 | self._position = ( | |
201 | self._original_position[0], | |
202 | self._original_position[1] - len(self._like_name) | |
203 | ) | |
204 | cached_name = None | |
205 | ||
206 | try: | |
207 | self.stack = stack = helpers.get_stack_at_position( | |
208 | grammar, self._code_lines, leaf, self._position | |
209 | ) | |
210 | except helpers.OnErrorLeaf as e: | |
211 | value = e.error_leaf.value | |
212 | if value == '.': | |
213 | # After ErrorLeaf's that are dots, we will not do any | |
214 | # completions since this probably just confuses the user. | |
215 | return cached_name, [] | |
216 | ||
217 | # If we don't have a value, just use global completion. | |
218 | return cached_name, self._complete_global_scope() | |
219 | ||
220 | allowed_transitions = \ | |
221 | list(stack._allowed_transition_names_and_token_types()) | |
222 | ||
223 | if 'if' in allowed_transitions: | |
224 | leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) | |
225 | previous_leaf = leaf.get_previous_leaf() | |
226 | ||
227 | indent = self._position[1] | |
228 | if not (leaf.start_pos <= self._position <= leaf.end_pos): | |
229 | indent = leaf.start_pos[1] | |
230 | ||
231 | if previous_leaf is not None: | |
232 | stmt = previous_leaf | |
233 | while True: | |
234 | stmt = search_ancestor( | |
235 | stmt, 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt', | |
236 | 'error_node', | |
237 | ) | |
238 | if stmt is None: | |
239 | break | |
240 | ||
241 | type_ = stmt.type | |
242 | if type_ == 'error_node': | |
243 | first = stmt.children[0] | |
244 | if isinstance(first, Leaf): | |
245 | type_ = first.value + '_stmt' | |
246 | # Compare indents | |
247 | if stmt.start_pos[1] == indent: | |
248 | if type_ == 'if_stmt': | |
249 | allowed_transitions += ['elif', 'else'] | |
250 | elif type_ == 'try_stmt': | |
251 | allowed_transitions += ['except', 'finally', 'else'] | |
252 | elif type_ == 'for_stmt': | |
253 | allowed_transitions.append('else') | |
254 | ||
255 | completion_names = [] | |
256 | ||
257 | kwargs_only = False | |
258 | if any(t in allowed_transitions for t in (PythonTokenTypes.NAME, | |
259 | PythonTokenTypes.INDENT)): | |
260 | # This means that we actually have to do type inference. | |
261 | ||
262 | nonterminals = [stack_node.nonterminal for stack_node in stack] | |
263 | ||
264 | nodes = _gather_nodes(stack) | |
265 | if nodes and nodes[-1] in ('as', 'def', 'class'): | |
266 | # No completions for ``with x as foo`` and ``import x as foo``. | |
267 | # Also true for defining names as a class or function. | |
268 | return cached_name, list(self._complete_inherited(is_function=True)) | |
269 | elif "import_stmt" in nonterminals: | |
270 | level, names = parse_dotted_names(nodes, "import_from" in nonterminals) | |
271 | ||
272 | only_modules = not ("import_from" in nonterminals and 'import' in nodes) | |
273 | completion_names += self._get_importer_names( | |
274 | names, | |
275 | level, | |
276 | only_modules=only_modules, | |
277 | ) | |
278 | elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.': | |
279 | dot = self._module_node.get_leaf_for_position(self._position) | |
280 | if dot.type == "endmarker": | |
281 | # This is a bit of a weird edge case, maybe we can somehow | |
282 | # generalize this. | |
283 | dot = leaf.get_previous_leaf() | |
284 | cached_name, n = self._complete_trailer(dot.get_previous_leaf()) | |
285 | completion_names += n | |
286 | elif self._is_parameter_completion(): | |
287 | completion_names += self._complete_params(leaf) | |
288 | else: | |
289 | # Apparently this looks like it's good enough to filter most cases | |
290 | # so that signature completions don't randomly appear. | |
291 | # To understand why this works, three things are important: | |
292 | # 1. trailer with a `,` in it is either a subscript or an arglist. | |
293 | # 2. If there's no `,`, it's at the start and only signatures start | |
294 | # with `(`. Other trailers could start with `.` or `[`. | |
295 | # 3. Decorators are very primitive and have an optional `(` with | |
296 | # optional arglist in them. | |
297 | if nodes[-1] in ['(', ','] \ | |
298 | and nonterminals[-1] in ('trailer', 'arglist', 'decorator'): | |
299 | signatures = self._signatures_callback(*self._position) | |
300 | if signatures: | |
301 | call_details = signatures[0]._call_details | |
302 | used_kwargs = list(call_details.iter_used_keyword_arguments()) | |
303 | positional_count = call_details.count_positional_arguments() | |
304 | ||
305 | completion_names += _get_signature_param_names( | |
306 | signatures, | |
307 | positional_count, | |
308 | used_kwargs, | |
309 | ) | |
310 | ||
311 | kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs) | |
312 | ||
313 | if not kwargs_only: | |
314 | completion_names += self._complete_global_scope() | |
315 | completion_names += self._complete_inherited(is_function=False) | |
316 | ||
317 | if not kwargs_only: | |
318 | current_line = self._code_lines[self._position[0] - 1][:self._position[1]] | |
319 | completion_names += self._complete_keywords( | |
320 | allowed_transitions, | |
321 | only_values=not (not current_line or current_line[-1] in ' \t.;' | |
322 | and current_line[-3:] != '...') | |
323 | ) | |
324 | ||
325 | return cached_name, completion_names | |
326 | ||
327 | def _is_parameter_completion(self): | |
328 | tos = self.stack[-1] | |
329 | if tos.nonterminal == 'lambdef' and len(tos.nodes) == 1: | |
330 | # We are at the position `lambda `, where basically the next node | |
331 | # is a param. | |
332 | return True | |
333 | if tos.nonterminal in 'parameters': | |
334 | # Basically we are at the position `foo(`, there's nothing there | |
335 | # yet, so we have no `typedargslist`. | |
336 | return True | |
337 | # var args is for lambdas and typed args for normal functions | |
338 | return tos.nonterminal in ('typedargslist', 'varargslist') and tos.nodes[-1] == ',' | |
339 | ||
340 | def _complete_params(self, leaf): | |
341 | stack_node = self.stack[-2] | |
342 | if stack_node.nonterminal == 'parameters': | |
343 | stack_node = self.stack[-3] | |
344 | if stack_node.nonterminal == 'funcdef': | |
345 | context = get_user_context(self._module_context, self._position) | |
346 | node = search_ancestor(leaf, 'error_node', 'funcdef') | |
347 | if node is not None: | |
348 | if node.type == 'error_node': | |
349 | n = node.children[0] | |
350 | if n.type == 'decorators': | |
351 | decorators = n.children | |
352 | elif n.type == 'decorator': | |
353 | decorators = [n] | |
354 | else: | |
355 | decorators = [] | |
356 | else: | |
357 | decorators = node.get_decorators() | |
358 | function_name = stack_node.nodes[1] | |
359 | ||
360 | return complete_param_names(context, function_name.value, decorators) | |
361 | return [] | |
362 | ||
363 | def _complete_keywords(self, allowed_transitions, only_values): | |
364 | for k in allowed_transitions: | |
365 | if isinstance(k, str) and k.isalpha(): | |
366 | if not only_values or k in ('True', 'False', 'None'): | |
367 | yield keywords.KeywordName(self._inference_state, k) | |
368 | ||
369 | def _complete_global_scope(self): | |
370 | context = get_user_context(self._module_context, self._position) | |
371 | debug.dbg('global completion scope: %s', context) | |
372 | flow_scope_node = get_flow_scope_node(self._module_node, self._position) | |
373 | filters = get_global_filters( | |
374 | context, | |
375 | self._position, | |
376 | flow_scope_node | |
377 | ) | |
378 | completion_names = [] | |
379 | for filter in filters: | |
380 | completion_names += filter.values() | |
381 | return completion_names | |
382 | ||
383 | def _complete_trailer(self, previous_leaf): | |
384 | inferred_context = self._module_context.create_context(previous_leaf) | |
385 | values = infer_call_of_leaf(inferred_context, previous_leaf) | |
386 | debug.dbg('trailer completion values: %s', values, color='MAGENTA') | |
387 | ||
388 | # The cached name simply exists to make speed optimizations for certain | |
389 | # modules. | |
390 | cached_name = None | |
391 | if len(values) == 1: | |
392 | v, = values | |
393 | if v.is_module(): | |
394 | if len(v.string_names) == 1: | |
395 | module_name = v.string_names[0] | |
396 | if module_name in ('numpy', 'tensorflow', 'matplotlib', 'pandas'): | |
397 | cached_name = module_name | |
398 | ||
399 | return cached_name, self._complete_trailer_for_values(values) | |
400 | ||
401 | def _complete_trailer_for_values(self, values): | |
402 | user_context = get_user_context(self._module_context, self._position) | |
403 | ||
404 | return complete_trailer(user_context, values) | |
405 | ||
406 | def _get_importer_names(self, names, level=0, only_modules=True): | |
407 | names = [n.value for n in names] | |
408 | i = imports.Importer(self._inference_state, names, self._module_context, level) | |
409 | return i.completion_names(self._inference_state, only_modules=only_modules) | |
410 | ||
411 | def _complete_inherited(self, is_function=True): | |
412 | """ | |
413 | Autocomplete inherited methods when overriding in child class. | |
414 | """ | |
415 | leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) | |
416 | cls = tree.search_ancestor(leaf, 'classdef') | |
417 | if cls is None: | |
418 | return | |
419 | ||
420 | # Complete the methods that are defined in the super classes. | |
421 | class_value = self._module_context.create_value(cls) | |
422 | ||
423 | if cls.start_pos[1] >= leaf.start_pos[1]: | |
424 | return | |
425 | ||
426 | filters = class_value.get_filters(is_instance=True) | |
427 | # The first dict is the dictionary of class itself. | |
428 | next(filters) | |
429 | for filter in filters: | |
430 | for name in filter.values(): | |
431 | # TODO we should probably check here for properties | |
432 | if (name.api_type == 'function') == is_function: | |
433 | yield name | |
434 | ||
435 | def _complete_in_string(self, start_leaf, string): | |
436 | """ | |
437 | To make it possible for people to have completions in doctests or | |
438 | generally in "Python" code in docstrings, we use the following | |
439 | heuristic: | |
440 | ||
441 | - Having an indented block of code | |
442 | - Having some doctest code that starts with `>>>` | |
443 | - Having backticks that doesn't have whitespace inside it | |
444 | """ | |
445 | def iter_relevant_lines(lines): | |
446 | include_next_line = False | |
447 | for l in code_lines: | |
448 | if include_next_line or l.startswith('>>>') or l.startswith(' '): | |
449 | yield re.sub(r'^( *>>> ?| +)', '', l) | |
450 | else: | |
451 | yield None | |
452 | ||
453 | include_next_line = bool(re.match(' *>>>', l)) | |
454 | ||
455 | string = dedent(string) | |
456 | code_lines = split_lines(string, keepends=True) | |
457 | relevant_code_lines = list(iter_relevant_lines(code_lines)) | |
458 | if relevant_code_lines[-1] is not None: | |
459 | # Some code lines might be None, therefore get rid of that. | |
460 | relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines] | |
461 | return self._complete_code_lines(relevant_code_lines) | |
462 | match = re.search(r'`([^`\s]+)', code_lines[-1]) | |
463 | if match: | |
464 | return self._complete_code_lines([match.group(1)]) | |
465 | return [] | |
466 | ||
467 | def _complete_code_lines(self, code_lines): | |
468 | module_node = self._inference_state.grammar.parse(''.join(code_lines)) | |
469 | module_value = DocstringModule( | |
470 | in_module_context=self._module_context, | |
471 | inference_state=self._inference_state, | |
472 | module_node=module_node, | |
473 | code_lines=code_lines, | |
474 | ) | |
475 | return Completion( | |
476 | self._inference_state, | |
477 | module_value.as_context(), | |
478 | code_lines=code_lines, | |
479 | position=module_node.end_pos, | |
480 | signatures_callback=lambda *args, **kwargs: [], | |
481 | fuzzy=self._fuzzy | |
482 | ).complete() | |
483 | ||
484 | ||
485 | def _gather_nodes(stack): | |
486 | nodes = [] | |
487 | for stack_node in stack: | |
488 | if stack_node.dfa.from_rule == 'small_stmt': | |
489 | nodes = [] | |
490 | else: | |
491 | nodes += stack_node.nodes | |
492 | return nodes | |
493 | ||
494 | ||
495 | _string_start = re.compile(r'^\w*(\'{3}|"{3}|\'|")') | |
496 | ||
497 | ||
498 | def _extract_string_while_in_string(leaf, position): | |
499 | def return_part_of_leaf(leaf): | |
500 | kwargs = {} | |
501 | if leaf.line == position[0]: | |
502 | kwargs['endpos'] = position[1] - leaf.column | |
503 | match = _string_start.match(leaf.value, **kwargs) | |
504 | if not match: | |
505 | return None, None, None | |
506 | start = match.group(0) | |
507 | if leaf.line == position[0] and position[1] < leaf.column + match.end(): | |
508 | return None, None, None | |
509 | return cut_value_at_position(leaf, position)[match.end():], leaf, start | |
510 | ||
511 | if position < leaf.start_pos: | |
512 | return None, None, None | |
513 | ||
514 | if leaf.type == 'string': | |
515 | return return_part_of_leaf(leaf) | |
516 | ||
517 | leaves = [] | |
518 | while leaf is not None: | |
519 | if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value): | |
520 | if len(leaf.value) > 1: | |
521 | return return_part_of_leaf(leaf) | |
522 | prefix_leaf = None | |
523 | if not leaf.prefix: | |
524 | prefix_leaf = leaf.get_previous_leaf() | |
525 | if prefix_leaf is None or prefix_leaf.type != 'name' \ | |
526 | or not all(c in 'rubf' for c in prefix_leaf.value.lower()): | |
527 | prefix_leaf = None | |
528 | ||
529 | return ( | |
530 | ''.join(cut_value_at_position(l, position) for l in leaves), | |
531 | prefix_leaf or leaf, | |
532 | ('' if prefix_leaf is None else prefix_leaf.value) | |
533 | + cut_value_at_position(leaf, position), | |
534 | ) | |
535 | if leaf.line != position[0]: | |
536 | # Multi line strings are always simple error leaves and contain the | |
537 | # whole string, single line error leaves are atherefore important | |
538 | # now and since the line is different, it's not really a single | |
539 | # line string anymore. | |
540 | break | |
541 | leaves.insert(0, leaf) | |
542 | leaf = leaf.get_previous_leaf() | |
543 | return None, None, None | |
544 | ||
545 | ||
546 | def complete_trailer(user_context, values): | |
547 | completion_names = [] | |
548 | for value in values: | |
549 | for filter in value.get_filters(origin_scope=user_context.tree_node): | |
550 | completion_names += filter.values() | |
551 | ||
552 | if not value.is_stub() and isinstance(value, TreeInstance): | |
553 | completion_names += _complete_getattr(user_context, value) | |
554 | ||
555 | python_values = convert_values(values) | |
556 | for c in python_values: | |
557 | if c not in values: | |
558 | for filter in c.get_filters(origin_scope=user_context.tree_node): | |
559 | completion_names += filter.values() | |
560 | return completion_names | |
561 | ||
562 | ||
563 | def _complete_getattr(user_context, instance): | |
564 | """ | |
565 | A heuristic to make completion for proxy objects work. This is not | |
566 | intended to work in all cases. It works exactly in this case: | |
567 | ||
568 | def __getattr__(self, name): | |
569 | ... | |
570 | return getattr(any_object, name) | |
571 | ||
572 | It is important that the return contains getattr directly, otherwise it | |
573 | won't work anymore. It's really just a stupid heuristic. It will not | |
574 | work if you write e.g. `return (getatr(o, name))`, because of the | |
575 | additional parentheses. It will also not work if you move the getattr | |
576 | to some other place that is not the return statement itself. | |
577 | ||
578 | It is intentional that it doesn't work in all cases. Generally it's | |
579 | really hard to do even this case (as you can see below). Most people | |
580 | will write it like this anyway and the other ones, well they are just | |
581 | out of luck I guess :) ~dave. | |
582 | """ | |
583 | names = (instance.get_function_slot_names('__getattr__') | |
584 | or instance.get_function_slot_names('__getattribute__')) | |
585 | functions = ValueSet.from_sets( | |
586 | name.infer() | |
587 | for name in names | |
588 | ) | |
589 | for func in functions: | |
590 | tree_node = func.tree_node | |
591 | if tree_node is None or tree_node.type != 'funcdef': | |
592 | continue | |
593 | ||
594 | for return_stmt in tree_node.iter_return_stmts(): | |
595 | # Basically until the next comment we just try to find out if a | |
596 | # return statement looks exactly like `return getattr(x, name)`. | |
597 | if return_stmt.type != 'return_stmt': | |
598 | continue | |
599 | atom_expr = return_stmt.children[1] | |
600 | if atom_expr.type != 'atom_expr': | |
601 | continue | |
602 | atom = atom_expr.children[0] | |
603 | trailer = atom_expr.children[1] | |
604 | if len(atom_expr.children) != 2 or atom.type != 'name' \ | |
605 | or atom.value != 'getattr': | |
606 | continue | |
607 | arglist = trailer.children[1] | |
608 | if arglist.type != 'arglist' or len(arglist.children) < 3: | |
609 | continue | |
610 | context = func.as_context() | |
611 | object_node = arglist.children[0] | |
612 | ||
613 | # Make sure it's a param: foo in __getattr__(self, foo) | |
614 | name_node = arglist.children[2] | |
615 | name_list = context.goto(name_node, name_node.start_pos) | |
616 | if not any(n.api_type == 'param' for n in name_list): | |
617 | continue | |
618 | ||
619 | # Now that we know that these are most probably completion | |
620 | # objects, we just infer the object and return them as | |
621 | # completions. | |
622 | objects = context.infer_node(object_node) | |
623 | return complete_trailer(user_context, objects) | |
624 | return [] | |
625 | ||
626 | ||
627 | def search_in_module(inference_state, module_context, names, wanted_names, | |
628 | wanted_type, complete=False, fuzzy=False, | |
629 | ignore_imports=False, convert=False): | |
630 | for s in wanted_names[:-1]: | |
631 | new_names = [] | |
632 | for n in names: | |
633 | if s == n.string_name: | |
634 | if n.tree_name is not None and n.api_type in ('module', 'namespace') \ | |
635 | and ignore_imports: | |
636 | continue | |
637 | new_names += complete_trailer( | |
638 | module_context, | |
639 | n.infer() | |
640 | ) | |
641 | debug.dbg('dot lookup on search %s from %s', new_names, names[:10]) | |
642 | names = new_names | |
643 | ||
644 | last_name = wanted_names[-1].lower() | |
645 | for n in names: | |
646 | string = n.string_name.lower() | |
647 | if complete and helpers.match(string, last_name, fuzzy=fuzzy) \ | |
648 | or not complete and string == last_name: | |
649 | if isinstance(n, SubModuleName): | |
650 | names = [v.name for v in n.infer()] | |
651 | else: | |
652 | names = [n] | |
653 | if convert: | |
654 | names = convert_names(names) | |
655 | for n2 in names: | |
656 | if complete: | |
657 | def_ = classes.Completion( | |
658 | inference_state, n2, | |
659 | stack=None, | |
660 | like_name_length=len(last_name), | |
661 | is_fuzzy=fuzzy, | |
662 | ) | |
663 | else: | |
664 | def_ = classes.Name(inference_state, n2) | |
665 | if not wanted_type or wanted_type == def_.type: | |
666 | yield def_ |