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