1from abc import abstractmethod
2from inspect import Parameter
3from typing import Optional, Tuple, Any
4
5from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
6from jedi.inference.base_value import ValueSet, NO_VALUES
7from jedi.inference.cache import inference_state_method_cache
8from jedi.inference import docstrings
9from jedi.cache import memoize_method
10from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
11from jedi.plugins import plugin_manager
12
13
14def _merge_name_docs(names):
15 doc = ''
16 for name in names:
17 if doc:
18 # In case we have multiple values, just return all of them
19 # separated by a few dashes.
20 doc += '\n' + '-' * 30 + '\n'
21 doc += name.py__doc__()
22 return doc
23
24
25class AbstractNameDefinition:
26 start_pos: Optional[Tuple[int, int]] = None
27 string_name: str
28 parent_context = None
29 tree_name = None
30 is_value_name = True
31 """
32 Used for the Jedi API to know if it's a keyword or an actual name.
33 """
34
35 @abstractmethod
36 def infer(self):
37 raise NotImplementedError
38
39 def goto(self):
40 # Typically names are already definitions and therefore a goto on that
41 # name will always result on itself.
42 return {self}
43
44 def get_qualified_names(self, include_module_names=False):
45 qualified_names = self._get_qualified_names()
46 if qualified_names is None or not include_module_names:
47 return qualified_names
48
49 module_names = self.get_root_context().string_names
50 if module_names is None:
51 return None
52 return module_names + qualified_names
53
54 def _get_qualified_names(self):
55 # By default, a name has no qualified names.
56 return None
57
58 def get_root_context(self):
59 return self.parent_context.get_root_context()
60
61 def get_public_name(self):
62 return self.string_name
63
64 def __repr__(self):
65 if self.start_pos is None:
66 return '<%s: string_name=%s>' % (self.__class__.__name__, self.string_name)
67 return '<%s: string_name=%s start_pos=%s>' % (self.__class__.__name__,
68 self.string_name, self.start_pos)
69
70 def is_import(self):
71 return False
72
73 def py__doc__(self):
74 return ''
75
76 @property
77 def api_type(self):
78 return self.parent_context.api_type
79
80 def get_defining_qualified_value(self):
81 """
82 Returns either None or the value that is public and qualified. Won't
83 return a function, because a name in a function is never public.
84 """
85 return None
86
87
88class AbstractArbitraryName(AbstractNameDefinition):
89 """
90 When you e.g. want to complete dicts keys, you probably want to complete
91 string literals, which is not really a name, but for Jedi we use this
92 concept of Name for completions as well.
93 """
94 is_value_name = False
95
96 def __init__(self, inference_state, string):
97 self.inference_state = inference_state
98 self.string_name = string
99 self.parent_context = inference_state.builtins_module
100
101 def infer(self):
102 return NO_VALUES
103
104
105class AbstractTreeName(AbstractNameDefinition):
106 tree_name: Any
107 parent_context: Any
108
109 def __init__(self, parent_context, tree_name):
110 self.parent_context = parent_context
111 self.tree_name = tree_name
112
113 def get_qualified_names(self, include_module_names=False):
114 import_node = self.tree_name.search_ancestor('import_name', 'import_from')
115 # For import nodes we cannot just have names, because it's very unclear
116 # how they would look like. For now we just ignore them in most cases.
117 # In case of level == 1, it works always, because it's like a submodule
118 # lookup.
119 if import_node is not None and not (import_node.level == 1
120 and self.get_root_context().get_value().is_package()):
121 # TODO improve the situation for when level is present.
122 if include_module_names and not import_node.level:
123 return tuple(n.value for n in import_node.get_path_for_name(self.tree_name))
124 else:
125 return None
126
127 return super().get_qualified_names(include_module_names)
128
129 def _get_qualified_names(self):
130 parent_names = self.parent_context.get_qualified_names()
131 if parent_names is None:
132 return None
133 return parent_names + (self.tree_name.value,)
134
135 def get_defining_qualified_value(self):
136 if self.is_import():
137 raise NotImplementedError("Shouldn't really happen, please report")
138 elif self.parent_context:
139 return self.parent_context.get_value() # Might be None
140 return None
141
142 def goto(self):
143 context = self.parent_context
144 name = self.tree_name
145 definition = name.get_definition(import_name_always=True)
146 if definition is not None:
147 type_ = definition.type
148 if type_ == 'expr_stmt':
149 # Only take the parent, because if it's more complicated than just
150 # a name it's something you can "goto" again.
151 is_simple_name = name.parent.type not in ('power', 'trailer')
152 if is_simple_name:
153 return [self]
154 elif type_ in ('import_from', 'import_name'):
155 from jedi.inference.imports import goto_import
156 module_names = goto_import(context, name)
157 return module_names
158 else:
159 return [self]
160 else:
161 from jedi.inference.imports import follow_error_node_imports_if_possible
162 values = follow_error_node_imports_if_possible(context, name)
163 if values is not None:
164 return [value.name for value in values]
165
166 par = name.parent
167 node_type = par.type
168 if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name:
169 # Named param goto.
170 trailer = par.parent
171 if trailer.type == 'arglist':
172 trailer = trailer.parent
173 if trailer.type == 'error_node':
174 return []
175 if trailer.type != 'classdef':
176 if trailer.type == 'decorator':
177 value_set = context.infer_node(trailer.children[1])
178 else:
179 i = trailer.parent.children.index(trailer)
180 to_infer = trailer.parent.children[:i]
181 if to_infer[0] == 'await':
182 to_infer.pop(0)
183 value_set = context.infer_node(to_infer[0])
184 from jedi.inference.syntax_tree import infer_trailer
185 for trailer in to_infer[1:]:
186 value_set = infer_trailer(context, value_set, trailer)
187 param_names = []
188 for value in value_set:
189 for signature in value.get_signatures():
190 for param_name in signature.get_param_names():
191 if param_name.string_name == name.value:
192 param_names.append(param_name)
193 return param_names
194 elif node_type == 'dotted_name': # Is a decorator.
195 index = par.children.index(name)
196 if index > 0:
197 new_dotted = deep_ast_copy(par)
198 new_dotted.children[index - 1:] = []
199 values = context.infer_node(new_dotted)
200 return [
201 n
202 for value in values
203 for n in value.goto(name, name_context=context)
204 ]
205
206 if node_type == 'trailer' and par.children[0] == '.':
207 values = infer_call_of_leaf(context, name, cut_own_trailer=True)
208 return values.goto(name, name_context=context)
209 else:
210 stmt = name.search_ancestor('expr_stmt', 'lambdef') or name
211 if stmt.type == 'lambdef':
212 stmt = name
213 return context.goto(name, position=stmt.start_pos)
214
215 def is_import(self):
216 imp = self.tree_name.search_ancestor('import_from', 'import_name')
217 return imp is not None
218
219 @property
220 def string_name(self):
221 return self.tree_name.value
222
223 @property
224 def start_pos(self):
225 return self.tree_name.start_pos
226
227
228class ValueNameMixin:
229 _value: Any
230 parent_context: Any
231
232 def infer(self):
233 return ValueSet([self._value])
234
235 def py__doc__(self):
236 doc = self._value.py__doc__()
237 if not doc and self._value.is_stub():
238 from jedi.inference.gradual.conversion import convert_names
239 names = convert_names([self], prefer_stub_to_compiled=False)
240 if self not in names:
241 return _merge_name_docs(names)
242 return doc
243
244 def _get_qualified_names(self):
245 return self._value.get_qualified_names()
246
247 def get_root_context(self):
248 if self.parent_context is None: # A module
249 return self._value.as_context()
250 return super().get_root_context() # type: ignore
251
252 def get_defining_qualified_value(self):
253 context = self.parent_context
254 if context is not None and (context.is_module() or context.is_class()):
255 return self.parent_context.get_value() # Might be None
256 return None
257
258 @property
259 def api_type(self):
260 return self._value.api_type
261
262
263class ValueName(ValueNameMixin, AbstractTreeName):
264 def __init__(self, value, tree_name):
265 super().__init__(value.parent_context, tree_name)
266 self._value = value
267
268 def goto(self):
269 return ValueSet([self._value.name])
270
271
272class TreeNameDefinition(AbstractTreeName):
273 _API_TYPES = dict(
274 import_name='module',
275 import_from='module',
276 funcdef='function',
277 param='param',
278 classdef='class',
279 )
280
281 def infer(self):
282 # Refactor this, should probably be here.
283 from jedi.inference.syntax_tree import tree_name_to_values
284 return tree_name_to_values(
285 self.parent_context.inference_state,
286 self.parent_context,
287 self.tree_name
288 )
289
290 @property
291 def api_type(self):
292 definition = self.tree_name.get_definition(import_name_always=True)
293 if definition is None:
294 return 'statement'
295 return self._API_TYPES.get(definition.type, 'statement')
296
297 def assignment_indexes(self):
298 """
299 Returns an array of tuple(int, node) of the indexes that are used in
300 tuple assignments.
301
302 For example if the name is ``y`` in the following code::
303
304 x, (y, z) = 2, ''
305
306 would result in ``[(1, xyz_node), (0, yz_node)]``.
307
308 When searching for b in the case ``a, *b, c = [...]`` it will return::
309
310 [(slice(1, -1), abc_node)]
311 """
312 indexes = []
313 is_star_expr = False
314 node = self.tree_name.parent
315 compare = self.tree_name
316 while node is not None:
317 if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'):
318 for i, child in enumerate(node.children):
319 if child == compare:
320 index = int(i / 2)
321 if is_star_expr:
322 from_end = int((len(node.children) - i) / 2)
323 index = slice(index, -from_end)
324 indexes.insert(0, (index, node))
325 break
326 else:
327 raise LookupError("Couldn't find the assignment.")
328 is_star_expr = False
329 elif node.type == 'star_expr':
330 is_star_expr = True
331 elif node.type in ('expr_stmt', 'sync_comp_for'):
332 break
333
334 compare = node
335 node = node.parent
336 return indexes
337
338 @property
339 def inference_state(self):
340 # Used by the cache function below
341 return self.parent_context.inference_state
342
343 @inference_state_method_cache(default='')
344 def py__doc__(self):
345 api_type = self.api_type
346 if api_type in ('function', 'class', 'property'):
347 if self.parent_context.get_root_context().is_stub():
348 from jedi.inference.gradual.conversion import convert_names
349 names = convert_names([self], prefer_stub_to_compiled=False)
350 if self not in names:
351 return _merge_name_docs(names)
352
353 # Make sure the names are not TreeNameDefinitions anymore.
354 return clean_scope_docstring(self.tree_name.get_definition())
355
356 if api_type == 'module':
357 names = self.goto()
358 if self not in names:
359 return _merge_name_docs(names)
360
361 if api_type == 'statement' and self.tree_name.is_definition():
362 return find_statement_documentation(self.tree_name.get_definition())
363 return ''
364
365
366class _ParamMixin:
367 get_kind: Any
368
369 def maybe_positional_argument(self, include_star=True):
370 options: list[int] = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
371 if include_star:
372 options.append(Parameter.VAR_POSITIONAL)
373 return self.get_kind() in options
374
375 def maybe_keyword_argument(self, include_stars=True):
376 options: list[int] = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
377 if include_stars:
378 options.append(Parameter.VAR_KEYWORD)
379 return self.get_kind() in options
380
381 def _kind_string(self):
382 kind = self.get_kind()
383 if kind == Parameter.VAR_POSITIONAL: # *args
384 return '*'
385 if kind == Parameter.VAR_KEYWORD: # **kwargs
386 return '**'
387 return ''
388
389 def get_qualified_names(self, include_module_names=False):
390 return None
391
392
393class ParamNameInterface(_ParamMixin):
394 api_type = 'param'
395
396 def get_kind(self):
397 raise NotImplementedError
398
399 def to_string(self):
400 raise NotImplementedError
401
402 def get_executed_param_name(self):
403 """
404 For dealing with type inference and working around the graph, we
405 sometimes want to have the param name of the execution. This feels a
406 bit strange and we might have to refactor at some point.
407
408 For now however it exists to avoid infering params when we don't really
409 need them (e.g. when we can just instead use annotations.
410 """
411 return None
412
413 @property
414 def star_count(self):
415 kind = self.get_kind()
416 if kind == Parameter.VAR_POSITIONAL:
417 return 1
418 if kind == Parameter.VAR_KEYWORD:
419 return 2
420 return 0
421
422 def infer_default(self):
423 return NO_VALUES
424
425
426class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
427 annotation_node = None
428 default_node = None
429
430 def to_string(self):
431 output = self._kind_string() + self.get_public_name()
432 annotation = self.annotation_node
433 default = self.default_node
434 if annotation is not None:
435 output += ': ' + annotation.get_code(include_prefix=False)
436 if default is not None:
437 output += '=' + default.get_code(include_prefix=False)
438 return output
439
440 def get_public_name(self):
441 name = self.string_name
442 if name.startswith('__'):
443 # Params starting with __ are an equivalent to positional only
444 # variables in typeshed.
445 name = name[2:]
446 return name
447
448 def goto(self, **kwargs):
449 return [self]
450
451
452class _ActualTreeParamName(BaseTreeParamName):
453 def __init__(self, function_value, tree_name):
454 super().__init__(
455 function_value.get_default_param_context(), tree_name)
456 self.function_value = function_value
457
458 def _get_param_node(self):
459 return self.tree_name.search_ancestor('param')
460
461 @property
462 def annotation_node(self):
463 return self._get_param_node().annotation
464
465 def infer_annotation(self, execute_annotation=True, ignore_stars=False):
466 from jedi.inference.gradual.annotation import infer_param
467 values = infer_param(
468 self.function_value, self._get_param_node(),
469 ignore_stars=ignore_stars)
470 if execute_annotation:
471 values = values.execute_annotation(self.function_value.get_default_param_context())
472 return values
473
474 def infer_default(self):
475 node = self.default_node
476 if node is None:
477 return NO_VALUES
478 return self.parent_context.infer_node(node)
479
480 @property
481 def default_node(self):
482 return self._get_param_node().default
483
484 def get_kind(self):
485 tree_param = self._get_param_node()
486 if tree_param.star_count == 1: # *args
487 return Parameter.VAR_POSITIONAL
488 if tree_param.star_count == 2: # **kwargs
489 return Parameter.VAR_KEYWORD
490
491 # Params starting with __ are an equivalent to positional only
492 # variables in typeshed.
493 if tree_param.name.value.startswith('__'):
494 return Parameter.POSITIONAL_ONLY
495
496 parent = tree_param.parent
497 param_appeared = False
498 for p in parent.children:
499 if param_appeared:
500 if p == '/':
501 return Parameter.POSITIONAL_ONLY
502 else:
503 if p == '*':
504 return Parameter.KEYWORD_ONLY
505 if p.type == 'param':
506 if p.star_count:
507 return Parameter.KEYWORD_ONLY
508 if p == tree_param:
509 param_appeared = True
510 return Parameter.POSITIONAL_OR_KEYWORD
511
512 def infer(self):
513 values = self.infer_annotation()
514 if values:
515 return values
516
517 doc_params = docstrings.infer_param(self.function_value, self._get_param_node())
518 return doc_params
519
520
521class AnonymousParamName(_ActualTreeParamName):
522 @plugin_manager.decorate(name='goto_anonymous_param')
523 def goto(self):
524 return super().goto()
525
526 @plugin_manager.decorate(name='infer_anonymous_param')
527 def infer(self):
528 values = super().infer()
529 if values:
530 return values
531 from jedi.inference.dynamic_params import dynamic_param_lookup
532 param = self._get_param_node()
533 values = dynamic_param_lookup(self.function_value, param.position_index)
534 if values:
535 return values
536
537 if param.star_count == 1:
538 from jedi.inference.value.iterable import FakeTuple
539 value = FakeTuple(self.function_value.inference_state, [])
540 elif param.star_count == 2:
541 from jedi.inference.value.iterable import FakeDict
542 value = FakeDict(self.function_value.inference_state, {})
543 elif param.default is None:
544 return NO_VALUES
545 else:
546 return self.function_value.parent_context.infer_node(param.default)
547 return ValueSet({value})
548
549
550class ParamName(_ActualTreeParamName):
551 def __init__(self, function_value, tree_name, arguments):
552 super().__init__(function_value, tree_name)
553 self.arguments = arguments
554
555 def infer(self):
556 values = super().infer()
557 if values:
558 return values
559
560 return self.get_executed_param_name().infer()
561
562 def get_executed_param_name(self):
563 from jedi.inference.param import get_executed_param_names
564 params_names = get_executed_param_names(self.function_value, self.arguments)
565 return params_names[self._get_param_node().position_index]
566
567
568class ParamNameWrapper(_ParamMixin):
569 def __init__(self, param_name):
570 self._wrapped_param_name = param_name
571
572 def __getattr__(self, name):
573 return getattr(self._wrapped_param_name, name)
574
575 def __repr__(self):
576 return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
577
578
579class ImportName(AbstractNameDefinition):
580 start_pos = (1, 0)
581 _level = 0
582
583 def __init__(self, parent_context, string_name):
584 self._from_module_context = parent_context
585 self.string_name = string_name
586
587 def get_qualified_names(self, include_module_names=False):
588 if include_module_names:
589 if self._level:
590 assert self._level == 1, "Everything else is not supported for now"
591 module_names = self._from_module_context.string_names
592 if module_names is None:
593 return module_names
594 return module_names + (self.string_name,)
595 return (self.string_name,)
596 return ()
597
598 @property
599 def parent_context(self):
600 m = self._from_module_context
601 import_values = self.infer()
602 if not import_values:
603 return m
604 # It's almost always possible to find the import or to not find it. The
605 # importing returns only one value, pretty much always.
606 return next(iter(import_values)).as_context()
607
608 @memoize_method
609 def infer(self):
610 from jedi.inference.imports import Importer
611 m = self._from_module_context
612 return Importer(m.inference_state, [self.string_name], m, level=self._level).follow()
613
614 def goto(self):
615 return [m.name for m in self.infer()]
616
617 @property
618 def api_type(self):
619 return 'module'
620
621 def py__doc__(self):
622 return _merge_name_docs(self.goto())
623
624
625class SubModuleName(ImportName):
626 _level = 1
627
628
629class NameWrapper:
630 def __init__(self, wrapped_name):
631 self._wrapped_name = wrapped_name
632
633 def __getattr__(self, name):
634 return getattr(self._wrapped_name, name)
635
636 def __repr__(self):
637 return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
638
639
640class StubNameMixin:
641 api_type: str
642 tree_name: Any
643 infer: Any
644
645 def py__doc__(self):
646 from jedi.inference.gradual.conversion import convert_names
647 # Stubs are not complicated and we can just follow simple statements
648 # that have an equals in them, because they typically make something
649 # else public. See e.g. stubs for `requests`.
650 names = [self]
651 if self.api_type == 'statement' and '=' in self.tree_name.get_definition().children:
652 names = [v.name for v in self.infer()]
653
654 names = convert_names(names, prefer_stub_to_compiled=False)
655 if self in names:
656 return super().py__doc__() # type: ignore
657 else:
658 # We have signatures ourselves in stubs, so don't use signatures
659 # from the implementation.
660 return _merge_name_docs(names)
661
662
663# From here on down we make looking up the sys.version_info fast.
664class StubName(StubNameMixin, TreeNameDefinition):
665 def infer(self):
666 inferred = super().infer()
667 if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
668 from jedi.inference.gradual.stub_value import VersionInfo
669 return ValueSet(VersionInfo(c) for c in inferred)
670 return inferred
671
672
673class ModuleName(ValueNameMixin, AbstractNameDefinition):
674 start_pos = 1, 0
675
676 def __init__(self, value, name):
677 self._value = value
678 self._name = name
679
680 @property
681 def string_name(self):
682 return self._name
683
684
685class StubModuleName(StubNameMixin, ModuleName):
686 pass