|
from abc import abstractmethod |
|
from inspect import Parameter |
|
from typing import Optional, Tuple |
|
|
|
from parso.tree import search_ancestor |
|
|
|
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring |
|
from jedi.inference.utils import unite |
|
from jedi.inference.base_value import ValueSet, NO_VALUES |
|
from jedi.inference.cache import inference_state_method_cache |
|
from jedi.inference import docstrings |
|
from jedi.cache import memoize_method |
|
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf |
|
from jedi.plugins import plugin_manager |
|
|
|
|
|
def _merge_name_docs(names): |
|
doc = '' |
|
for name in names: |
|
if doc: |
|
|
|
|
|
doc += '\n' + '-' * 30 + '\n' |
|
doc += name.py__doc__() |
|
return doc |
|
|
|
|
|
class AbstractNameDefinition: |
|
start_pos: Optional[Tuple[int, int]] = None |
|
string_name: str |
|
parent_context = None |
|
tree_name = None |
|
is_value_name = True |
|
""" |
|
Used for the Jedi API to know if it's a keyword or an actual name. |
|
""" |
|
|
|
@abstractmethod |
|
def infer(self): |
|
raise NotImplementedError |
|
|
|
@abstractmethod |
|
def goto(self): |
|
|
|
|
|
return {self} |
|
|
|
def get_qualified_names(self, include_module_names=False): |
|
qualified_names = self._get_qualified_names() |
|
if qualified_names is None or not include_module_names: |
|
return qualified_names |
|
|
|
module_names = self.get_root_context().string_names |
|
if module_names is None: |
|
return None |
|
return module_names + qualified_names |
|
|
|
def _get_qualified_names(self): |
|
|
|
return None |
|
|
|
def get_root_context(self): |
|
return self.parent_context.get_root_context() |
|
|
|
def get_public_name(self): |
|
return self.string_name |
|
|
|
def __repr__(self): |
|
if self.start_pos is None: |
|
return '<%s: string_name=%s>' % (self.__class__.__name__, self.string_name) |
|
return '<%s: string_name=%s start_pos=%s>' % (self.__class__.__name__, |
|
self.string_name, self.start_pos) |
|
|
|
def is_import(self): |
|
return False |
|
|
|
def py__doc__(self): |
|
return '' |
|
|
|
@property |
|
def api_type(self): |
|
return self.parent_context.api_type |
|
|
|
def get_defining_qualified_value(self): |
|
""" |
|
Returns either None or the value that is public and qualified. Won't |
|
return a function, because a name in a function is never public. |
|
""" |
|
return None |
|
|
|
|
|
class AbstractArbitraryName(AbstractNameDefinition): |
|
""" |
|
When you e.g. want to complete dicts keys, you probably want to complete |
|
string literals, which is not really a name, but for Jedi we use this |
|
concept of Name for completions as well. |
|
""" |
|
is_value_name = False |
|
|
|
def __init__(self, inference_state, string): |
|
self.inference_state = inference_state |
|
self.string_name = string |
|
self.parent_context = inference_state.builtins_module |
|
|
|
def infer(self): |
|
return NO_VALUES |
|
|
|
|
|
class AbstractTreeName(AbstractNameDefinition): |
|
def __init__(self, parent_context, tree_name): |
|
self.parent_context = parent_context |
|
self.tree_name = tree_name |
|
|
|
def get_qualified_names(self, include_module_names=False): |
|
import_node = search_ancestor(self.tree_name, 'import_name', 'import_from') |
|
|
|
|
|
|
|
|
|
if import_node is not None and not (import_node.level == 1 |
|
and self.get_root_context().get_value().is_package()): |
|
|
|
if include_module_names and not import_node.level: |
|
return tuple(n.value for n in import_node.get_path_for_name(self.tree_name)) |
|
else: |
|
return None |
|
|
|
return super().get_qualified_names(include_module_names) |
|
|
|
def _get_qualified_names(self): |
|
parent_names = self.parent_context.get_qualified_names() |
|
if parent_names is None: |
|
return None |
|
return parent_names + (self.tree_name.value,) |
|
|
|
def get_defining_qualified_value(self): |
|
if self.is_import(): |
|
raise NotImplementedError("Shouldn't really happen, please report") |
|
elif self.parent_context: |
|
return self.parent_context.get_value() |
|
return None |
|
|
|
def goto(self): |
|
context = self.parent_context |
|
name = self.tree_name |
|
definition = name.get_definition(import_name_always=True) |
|
if definition is not None: |
|
type_ = definition.type |
|
if type_ == 'expr_stmt': |
|
|
|
|
|
is_simple_name = name.parent.type not in ('power', 'trailer') |
|
if is_simple_name: |
|
return [self] |
|
elif type_ in ('import_from', 'import_name'): |
|
from jedi.inference.imports import goto_import |
|
module_names = goto_import(context, name) |
|
return module_names |
|
else: |
|
return [self] |
|
else: |
|
from jedi.inference.imports import follow_error_node_imports_if_possible |
|
values = follow_error_node_imports_if_possible(context, name) |
|
if values is not None: |
|
return [value.name for value in values] |
|
|
|
par = name.parent |
|
node_type = par.type |
|
if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name: |
|
|
|
trailer = par.parent |
|
if trailer.type == 'arglist': |
|
trailer = trailer.parent |
|
if trailer.type != 'classdef': |
|
if trailer.type == 'decorator': |
|
value_set = context.infer_node(trailer.children[1]) |
|
else: |
|
i = trailer.parent.children.index(trailer) |
|
to_infer = trailer.parent.children[:i] |
|
if to_infer[0] == 'await': |
|
to_infer.pop(0) |
|
value_set = context.infer_node(to_infer[0]) |
|
from jedi.inference.syntax_tree import infer_trailer |
|
for trailer in to_infer[1:]: |
|
value_set = infer_trailer(context, value_set, trailer) |
|
param_names = [] |
|
for value in value_set: |
|
for signature in value.get_signatures(): |
|
for param_name in signature.get_param_names(): |
|
if param_name.string_name == name.value: |
|
param_names.append(param_name) |
|
return param_names |
|
elif node_type == 'dotted_name': |
|
index = par.children.index(name) |
|
if index > 0: |
|
new_dotted = deep_ast_copy(par) |
|
new_dotted.children[index - 1:] = [] |
|
values = context.infer_node(new_dotted) |
|
return unite( |
|
value.goto(name, name_context=context) |
|
for value in values |
|
) |
|
|
|
if node_type == 'trailer' and par.children[0] == '.': |
|
values = infer_call_of_leaf(context, name, cut_own_trailer=True) |
|
return values.goto(name, name_context=context) |
|
else: |
|
stmt = search_ancestor( |
|
name, 'expr_stmt', 'lambdef' |
|
) or name |
|
if stmt.type == 'lambdef': |
|
stmt = name |
|
return context.goto(name, position=stmt.start_pos) |
|
|
|
def is_import(self): |
|
imp = search_ancestor(self.tree_name, 'import_from', 'import_name') |
|
return imp is not None |
|
|
|
@property |
|
def string_name(self): |
|
return self.tree_name.value |
|
|
|
@property |
|
def start_pos(self): |
|
return self.tree_name.start_pos |
|
|
|
|
|
class ValueNameMixin: |
|
def infer(self): |
|
return ValueSet([self._value]) |
|
|
|
def py__doc__(self): |
|
doc = self._value.py__doc__() |
|
if not doc and self._value.is_stub(): |
|
from jedi.inference.gradual.conversion import convert_names |
|
names = convert_names([self], prefer_stub_to_compiled=False) |
|
if self not in names: |
|
return _merge_name_docs(names) |
|
return doc |
|
|
|
def _get_qualified_names(self): |
|
return self._value.get_qualified_names() |
|
|
|
def get_root_context(self): |
|
if self.parent_context is None: |
|
return self._value.as_context() |
|
return super().get_root_context() |
|
|
|
def get_defining_qualified_value(self): |
|
context = self.parent_context |
|
if context is not None and (context.is_module() or context.is_class()): |
|
return self.parent_context.get_value() |
|
return None |
|
|
|
@property |
|
def api_type(self): |
|
return self._value.api_type |
|
|
|
|
|
class ValueName(ValueNameMixin, AbstractTreeName): |
|
def __init__(self, value, tree_name): |
|
super().__init__(value.parent_context, tree_name) |
|
self._value = value |
|
|
|
def goto(self): |
|
return ValueSet([self._value.name]) |
|
|
|
|
|
class TreeNameDefinition(AbstractTreeName): |
|
_API_TYPES = dict( |
|
import_name='module', |
|
import_from='module', |
|
funcdef='function', |
|
param='param', |
|
classdef='class', |
|
) |
|
|
|
def infer(self): |
|
|
|
from jedi.inference.syntax_tree import tree_name_to_values |
|
return tree_name_to_values( |
|
self.parent_context.inference_state, |
|
self.parent_context, |
|
self.tree_name |
|
) |
|
|
|
@property |
|
def api_type(self): |
|
definition = self.tree_name.get_definition(import_name_always=True) |
|
if definition is None: |
|
return 'statement' |
|
return self._API_TYPES.get(definition.type, 'statement') |
|
|
|
def assignment_indexes(self): |
|
""" |
|
Returns an array of tuple(int, node) of the indexes that are used in |
|
tuple assignments. |
|
|
|
For example if the name is ``y`` in the following code:: |
|
|
|
x, (y, z) = 2, '' |
|
|
|
would result in ``[(1, xyz_node), (0, yz_node)]``. |
|
|
|
When searching for b in the case ``a, *b, c = [...]`` it will return:: |
|
|
|
[(slice(1, -1), abc_node)] |
|
""" |
|
indexes = [] |
|
is_star_expr = False |
|
node = self.tree_name.parent |
|
compare = self.tree_name |
|
while node is not None: |
|
if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'): |
|
for i, child in enumerate(node.children): |
|
if child == compare: |
|
index = int(i / 2) |
|
if is_star_expr: |
|
from_end = int((len(node.children) - i) / 2) |
|
index = slice(index, -from_end) |
|
indexes.insert(0, (index, node)) |
|
break |
|
else: |
|
raise LookupError("Couldn't find the assignment.") |
|
is_star_expr = False |
|
elif node.type == 'star_expr': |
|
is_star_expr = True |
|
elif node.type in ('expr_stmt', 'sync_comp_for'): |
|
break |
|
|
|
compare = node |
|
node = node.parent |
|
return indexes |
|
|
|
@property |
|
def inference_state(self): |
|
|
|
return self.parent_context.inference_state |
|
|
|
@inference_state_method_cache(default='') |
|
def py__doc__(self): |
|
api_type = self.api_type |
|
if api_type in ('function', 'class', 'property'): |
|
if self.parent_context.get_root_context().is_stub(): |
|
from jedi.inference.gradual.conversion import convert_names |
|
names = convert_names([self], prefer_stub_to_compiled=False) |
|
if self not in names: |
|
return _merge_name_docs(names) |
|
|
|
|
|
return clean_scope_docstring(self.tree_name.get_definition()) |
|
|
|
if api_type == 'module': |
|
names = self.goto() |
|
if self not in names: |
|
return _merge_name_docs(names) |
|
|
|
if api_type == 'statement' and self.tree_name.is_definition(): |
|
return find_statement_documentation(self.tree_name.get_definition()) |
|
return '' |
|
|
|
|
|
class _ParamMixin: |
|
def maybe_positional_argument(self, include_star=True): |
|
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] |
|
if include_star: |
|
options.append(Parameter.VAR_POSITIONAL) |
|
return self.get_kind() in options |
|
|
|
def maybe_keyword_argument(self, include_stars=True): |
|
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD] |
|
if include_stars: |
|
options.append(Parameter.VAR_KEYWORD) |
|
return self.get_kind() in options |
|
|
|
def _kind_string(self): |
|
kind = self.get_kind() |
|
if kind == Parameter.VAR_POSITIONAL: |
|
return '*' |
|
if kind == Parameter.VAR_KEYWORD: |
|
return '**' |
|
return '' |
|
|
|
def get_qualified_names(self, include_module_names=False): |
|
return None |
|
|
|
|
|
class ParamNameInterface(_ParamMixin): |
|
api_type = 'param' |
|
|
|
def get_kind(self): |
|
raise NotImplementedError |
|
|
|
def to_string(self): |
|
raise NotImplementedError |
|
|
|
def get_executed_param_name(self): |
|
""" |
|
For dealing with type inference and working around the graph, we |
|
sometimes want to have the param name of the execution. This feels a |
|
bit strange and we might have to refactor at some point. |
|
|
|
For now however it exists to avoid infering params when we don't really |
|
need them (e.g. when we can just instead use annotations. |
|
""" |
|
return None |
|
|
|
@property |
|
def star_count(self): |
|
kind = self.get_kind() |
|
if kind == Parameter.VAR_POSITIONAL: |
|
return 1 |
|
if kind == Parameter.VAR_KEYWORD: |
|
return 2 |
|
return 0 |
|
|
|
def infer_default(self): |
|
return NO_VALUES |
|
|
|
|
|
class BaseTreeParamName(ParamNameInterface, AbstractTreeName): |
|
annotation_node = None |
|
default_node = None |
|
|
|
def to_string(self): |
|
output = self._kind_string() + self.get_public_name() |
|
annotation = self.annotation_node |
|
default = self.default_node |
|
if annotation is not None: |
|
output += ': ' + annotation.get_code(include_prefix=False) |
|
if default is not None: |
|
output += '=' + default.get_code(include_prefix=False) |
|
return output |
|
|
|
def get_public_name(self): |
|
name = self.string_name |
|
if name.startswith('__'): |
|
|
|
|
|
name = name[2:] |
|
return name |
|
|
|
def goto(self, **kwargs): |
|
return [self] |
|
|
|
|
|
class _ActualTreeParamName(BaseTreeParamName): |
|
def __init__(self, function_value, tree_name): |
|
super().__init__( |
|
function_value.get_default_param_context(), tree_name) |
|
self.function_value = function_value |
|
|
|
def _get_param_node(self): |
|
return search_ancestor(self.tree_name, 'param') |
|
|
|
@property |
|
def annotation_node(self): |
|
return self._get_param_node().annotation |
|
|
|
def infer_annotation(self, execute_annotation=True, ignore_stars=False): |
|
from jedi.inference.gradual.annotation import infer_param |
|
values = infer_param( |
|
self.function_value, self._get_param_node(), |
|
ignore_stars=ignore_stars) |
|
if execute_annotation: |
|
values = values.execute_annotation() |
|
return values |
|
|
|
def infer_default(self): |
|
node = self.default_node |
|
if node is None: |
|
return NO_VALUES |
|
return self.parent_context.infer_node(node) |
|
|
|
@property |
|
def default_node(self): |
|
return self._get_param_node().default |
|
|
|
def get_kind(self): |
|
tree_param = self._get_param_node() |
|
if tree_param.star_count == 1: |
|
return Parameter.VAR_POSITIONAL |
|
if tree_param.star_count == 2: |
|
return Parameter.VAR_KEYWORD |
|
|
|
|
|
|
|
if tree_param.name.value.startswith('__'): |
|
return Parameter.POSITIONAL_ONLY |
|
|
|
parent = tree_param.parent |
|
param_appeared = False |
|
for p in parent.children: |
|
if param_appeared: |
|
if p == '/': |
|
return Parameter.POSITIONAL_ONLY |
|
else: |
|
if p == '*': |
|
return Parameter.KEYWORD_ONLY |
|
if p.type == 'param': |
|
if p.star_count: |
|
return Parameter.KEYWORD_ONLY |
|
if p == tree_param: |
|
param_appeared = True |
|
return Parameter.POSITIONAL_OR_KEYWORD |
|
|
|
def infer(self): |
|
values = self.infer_annotation() |
|
if values: |
|
return values |
|
|
|
doc_params = docstrings.infer_param(self.function_value, self._get_param_node()) |
|
return doc_params |
|
|
|
|
|
class AnonymousParamName(_ActualTreeParamName): |
|
@plugin_manager.decorate(name='goto_anonymous_param') |
|
def goto(self): |
|
return super().goto() |
|
|
|
@plugin_manager.decorate(name='infer_anonymous_param') |
|
def infer(self): |
|
values = super().infer() |
|
if values: |
|
return values |
|
from jedi.inference.dynamic_params import dynamic_param_lookup |
|
param = self._get_param_node() |
|
values = dynamic_param_lookup(self.function_value, param.position_index) |
|
if values: |
|
return values |
|
|
|
if param.star_count == 1: |
|
from jedi.inference.value.iterable import FakeTuple |
|
value = FakeTuple(self.function_value.inference_state, []) |
|
elif param.star_count == 2: |
|
from jedi.inference.value.iterable import FakeDict |
|
value = FakeDict(self.function_value.inference_state, {}) |
|
elif param.default is None: |
|
return NO_VALUES |
|
else: |
|
return self.function_value.parent_context.infer_node(param.default) |
|
return ValueSet({value}) |
|
|
|
|
|
class ParamName(_ActualTreeParamName): |
|
def __init__(self, function_value, tree_name, arguments): |
|
super().__init__(function_value, tree_name) |
|
self.arguments = arguments |
|
|
|
def infer(self): |
|
values = super().infer() |
|
if values: |
|
return values |
|
|
|
return self.get_executed_param_name().infer() |
|
|
|
def get_executed_param_name(self): |
|
from jedi.inference.param import get_executed_param_names |
|
params_names = get_executed_param_names(self.function_value, self.arguments) |
|
return params_names[self._get_param_node().position_index] |
|
|
|
|
|
class ParamNameWrapper(_ParamMixin): |
|
def __init__(self, param_name): |
|
self._wrapped_param_name = param_name |
|
|
|
def __getattr__(self, name): |
|
return getattr(self._wrapped_param_name, name) |
|
|
|
def __repr__(self): |
|
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name) |
|
|
|
|
|
class ImportName(AbstractNameDefinition): |
|
start_pos = (1, 0) |
|
_level = 0 |
|
|
|
def __init__(self, parent_context, string_name): |
|
self._from_module_context = parent_context |
|
self.string_name = string_name |
|
|
|
def get_qualified_names(self, include_module_names=False): |
|
if include_module_names: |
|
if self._level: |
|
assert self._level == 1, "Everything else is not supported for now" |
|
module_names = self._from_module_context.string_names |
|
if module_names is None: |
|
return module_names |
|
return module_names + (self.string_name,) |
|
return (self.string_name,) |
|
return () |
|
|
|
@property |
|
def parent_context(self): |
|
m = self._from_module_context |
|
import_values = self.infer() |
|
if not import_values: |
|
return m |
|
|
|
|
|
return next(iter(import_values)).as_context() |
|
|
|
@memoize_method |
|
def infer(self): |
|
from jedi.inference.imports import Importer |
|
m = self._from_module_context |
|
return Importer(m.inference_state, [self.string_name], m, level=self._level).follow() |
|
|
|
def goto(self): |
|
return [m.name for m in self.infer()] |
|
|
|
@property |
|
def api_type(self): |
|
return 'module' |
|
|
|
def py__doc__(self): |
|
return _merge_name_docs(self.goto()) |
|
|
|
|
|
class SubModuleName(ImportName): |
|
_level = 1 |
|
|
|
|
|
class NameWrapper: |
|
def __init__(self, wrapped_name): |
|
self._wrapped_name = wrapped_name |
|
|
|
def __getattr__(self, name): |
|
return getattr(self._wrapped_name, name) |
|
|
|
def __repr__(self): |
|
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) |
|
|
|
|
|
class StubNameMixin: |
|
def py__doc__(self): |
|
from jedi.inference.gradual.conversion import convert_names |
|
|
|
|
|
|
|
names = [self] |
|
if self.api_type == 'statement' and '=' in self.tree_name.get_definition().children: |
|
names = [v.name for v in self.infer()] |
|
|
|
names = convert_names(names, prefer_stub_to_compiled=False) |
|
if self in names: |
|
return super().py__doc__() |
|
else: |
|
|
|
|
|
return _merge_name_docs(names) |
|
|
|
|
|
|
|
class StubName(StubNameMixin, TreeNameDefinition): |
|
def infer(self): |
|
inferred = super().infer() |
|
if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys': |
|
from jedi.inference.gradual.stub_value import VersionInfo |
|
return ValueSet(VersionInfo(c) for c in inferred) |
|
return inferred |
|
|
|
|
|
class ModuleName(ValueNameMixin, AbstractNameDefinition): |
|
start_pos = 1, 0 |
|
|
|
def __init__(self, value, name): |
|
self._value = value |
|
self._name = name |
|
|
|
@property |
|
def string_name(self): |
|
return self._name |
|
|
|
|
|
class StubModuleName(StubNameMixin, ModuleName): |
|
pass |
|
|