| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- 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:
- # In case we have multiple values, just return all of them
- # separated by a few dashes.
- 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):
- # Typically names are already definitions and therefore a goto on that
- # name will always result on itself.
- 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):
- # By default, a name has no qualified names.
- 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')
- # For import nodes we cannot just have names, because it's very unclear
- # how they would look like. For now we just ignore them in most cases.
- # In case of level == 1, it works always, because it's like a submodule
- # lookup.
- if import_node is not None and not (import_node.level == 1
- and self.get_root_context().get_value().is_package()):
- # TODO improve the situation for when level is present.
- 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() # Might be None
- 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':
- # Only take the parent, because if it's more complicated than just
- # a name it's something you can "goto" again.
- 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:
- # Named param goto.
- 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': # Is a decorator.
- 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: # A module
- 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() # Might be None
- 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):
- # Refactor this, should probably be here.
- 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):
- # Used by the cache function below
- 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)
- # Make sure the names are not TreeNameDefinitions anymore.
- 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: # *args
- return '*'
- if kind == Parameter.VAR_KEYWORD: # **kwargs
- 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('__'):
- # Params starting with __ are an equivalent to positional only
- # variables in typeshed.
- 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: # *args
- return Parameter.VAR_POSITIONAL
- if tree_param.star_count == 2: # **kwargs
- return Parameter.VAR_KEYWORD
- # Params starting with __ are an equivalent to positional only
- # variables in typeshed.
- 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
- # It's almost always possible to find the import or to not find it. The
- # importing returns only one value, pretty much always.
- 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
- # Stubs are not complicated and we can just follow simple statements
- # that have an equals in them, because they typically make something
- # else public. See e.g. stubs for `requests`.
- 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:
- # We have signatures ourselves in stubs, so don't use signatures
- # from the implementation.
- return _merge_name_docs(names)
- # From here on down we make looking up the sys.version_info fast.
- 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
|