| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- """
- Imitate the parser representation.
- """
- import re
- from functools import partial
- from inspect import Parameter
- from pathlib import Path
- from typing import Optional
- from jedi import debug
- from jedi.inference.utils import to_list
- from jedi.cache import memoize_method
- from jedi.inference.filters import AbstractFilter
- from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
- ParamNameInterface
- from jedi.inference.base_value import Value, ValueSet, NO_VALUES
- from jedi.inference.lazy_value import LazyKnownValue
- from jedi.inference.compiled.access import _sentinel
- from jedi.inference.cache import inference_state_function_cache
- from jedi.inference.helpers import reraise_getitem_errors
- from jedi.inference.signature import BuiltinSignature
- from jedi.inference.context import CompiledContext, CompiledModuleContext
- class CheckAttribute:
- """Raises :exc:`AttributeError` if the attribute X is not available."""
- def __init__(self, check_name=None):
- # Remove the py in front of e.g. py__call__.
- self.check_name = check_name
- def __call__(self, func):
- self.func = func
- if self.check_name is None:
- self.check_name = func.__name__[2:]
- return self
- def __get__(self, instance, owner):
- if instance is None:
- return self
- # This might raise an AttributeError. That's wanted.
- instance.access_handle.getattr_paths(self.check_name)
- return partial(self.func, instance)
- class CompiledValue(Value):
- def __init__(self, inference_state, access_handle, parent_context=None):
- super().__init__(inference_state, parent_context)
- self.access_handle = access_handle
- def py__call__(self, arguments):
- return_annotation = self.access_handle.get_return_annotation()
- if return_annotation is not None:
- return create_from_access_path(
- self.inference_state,
- return_annotation
- ).execute_annotation()
- try:
- self.access_handle.getattr_paths('__call__')
- except AttributeError:
- return super().py__call__(arguments)
- else:
- if self.access_handle.is_class():
- from jedi.inference.value import CompiledInstance
- return ValueSet([
- CompiledInstance(self.inference_state, self.parent_context, self, arguments)
- ])
- else:
- return ValueSet(self._execute_function(arguments))
- @CheckAttribute()
- def py__class__(self):
- return create_from_access_path(self.inference_state, self.access_handle.py__class__())
- @CheckAttribute()
- def py__mro__(self):
- return (self,) + tuple(
- create_from_access_path(self.inference_state, access)
- for access in self.access_handle.py__mro__accesses()
- )
- @CheckAttribute()
- def py__bases__(self):
- return tuple(
- create_from_access_path(self.inference_state, access)
- for access in self.access_handle.py__bases__()
- )
- def get_qualified_names(self):
- return self.access_handle.get_qualified_names()
- def py__bool__(self):
- return self.access_handle.py__bool__()
- def is_class(self):
- return self.access_handle.is_class()
- def is_function(self):
- return self.access_handle.is_function()
- def is_module(self):
- return self.access_handle.is_module()
- def is_compiled(self):
- return True
- def is_stub(self):
- return False
- def is_instance(self):
- return self.access_handle.is_instance()
- def py__doc__(self):
- return self.access_handle.py__doc__()
- @to_list
- def get_param_names(self):
- try:
- signature_params = self.access_handle.get_signature_params()
- except ValueError: # Has no signature
- params_str, ret = self._parse_function_doc()
- if not params_str:
- tokens = []
- else:
- tokens = params_str.split(',')
- if self.access_handle.ismethoddescriptor():
- tokens.insert(0, 'self')
- for p in tokens:
- name, _, default = p.strip().partition('=')
- yield UnresolvableParamName(self, name, default)
- else:
- for signature_param in signature_params:
- yield SignatureParamName(self, signature_param)
- def get_signatures(self):
- _, return_string = self._parse_function_doc()
- return [BuiltinSignature(self, return_string)]
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr())
- @memoize_method
- def _parse_function_doc(self):
- doc = self.py__doc__()
- if doc is None:
- return '', ''
- return _parse_function_doc(doc)
- @property
- def api_type(self):
- return self.access_handle.get_api_type()
- def get_filters(self, is_instance=False, origin_scope=None):
- yield self._ensure_one_filter(is_instance)
- @memoize_method
- def _ensure_one_filter(self, is_instance):
- return CompiledValueFilter(self.inference_state, self, is_instance)
- def py__simple_getitem__(self, index):
- with reraise_getitem_errors(IndexError, KeyError, TypeError):
- try:
- access = self.access_handle.py__simple_getitem__(
- index,
- safe=not self.inference_state.allow_unsafe_executions
- )
- except AttributeError:
- return super().py__simple_getitem__(index)
- if access is None:
- return super().py__simple_getitem__(index)
- return ValueSet([create_from_access_path(self.inference_state, access)])
- def py__getitem__(self, index_value_set, contextualized_node):
- all_access_paths = self.access_handle.py__getitem__all_values()
- if all_access_paths is None:
- # This means basically that no __getitem__ has been defined on this
- # object.
- return super().py__getitem__(index_value_set, contextualized_node)
- return ValueSet(
- create_from_access_path(self.inference_state, access)
- for access in all_access_paths
- )
- def py__iter__(self, contextualized_node=None):
- if not self.access_handle.has_iter():
- yield from super().py__iter__(contextualized_node)
- access_path_list = self.access_handle.py__iter__list()
- if access_path_list is None:
- # There is no __iter__ method on this object.
- return
- for access in access_path_list:
- yield LazyKnownValue(create_from_access_path(self.inference_state, access))
- def py__name__(self):
- return self.access_handle.py__name__()
- @property
- def name(self):
- name = self.py__name__()
- if name is None:
- name = self.access_handle.get_repr()
- return CompiledValueName(self, name)
- def _execute_function(self, params):
- from jedi.inference import docstrings
- from jedi.inference.compiled import builtin_from_name
- if self.api_type != 'function':
- return
- for name in self._parse_function_doc()[1].split():
- try:
- # TODO wtf is this? this is exactly the same as the thing
- # below. It uses getattr as well.
- self.inference_state.builtins_module.access_handle.getattr_paths(name)
- except AttributeError:
- continue
- else:
- bltn_obj = builtin_from_name(self.inference_state, name)
- yield from self.inference_state.execute(bltn_obj, params)
- yield from docstrings.infer_return_types(self)
- def get_safe_value(self, default=_sentinel):
- try:
- return self.access_handle.get_safe_value()
- except ValueError:
- if default == _sentinel:
- raise
- return default
- def execute_operation(self, other, operator):
- try:
- return ValueSet([create_from_access_path(
- self.inference_state,
- self.access_handle.execute_operation(other.access_handle, operator)
- )])
- except TypeError:
- return NO_VALUES
- def execute_annotation(self):
- if self.access_handle.get_repr() == 'None':
- # None as an annotation doesn't need to be executed.
- return ValueSet([self])
- name, args = self.access_handle.get_annotation_name_and_args()
- arguments = [
- ValueSet([create_from_access_path(self.inference_state, path)])
- for path in args
- ]
- if name == 'Union':
- return ValueSet.from_sets(arg.execute_annotation() for arg in arguments)
- elif name:
- # While with_generics only exists on very specific objects, we
- # should probably be fine, because we control all the typing
- # objects.
- return ValueSet([
- v.with_generics(arguments)
- for v in self.inference_state.typing_module.py__getattribute__(name)
- ]).execute_annotation()
- return super().execute_annotation()
- def negate(self):
- return create_from_access_path(self.inference_state, self.access_handle.negate())
- def get_metaclasses(self):
- return NO_VALUES
- def _as_context(self):
- return CompiledContext(self)
- @property
- def array_type(self):
- return self.access_handle.get_array_type()
- def get_key_values(self):
- return [
- create_from_access_path(self.inference_state, k)
- for k in self.access_handle.get_key_paths()
- ]
- def get_type_hint(self, add_class_info=True):
- if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
- return 'None'
- return None
- class CompiledModule(CompiledValue):
- file_io = None # For modules
- def _as_context(self):
- return CompiledModuleContext(self)
- def py__path__(self):
- return self.access_handle.py__path__()
- def is_package(self):
- return self.py__path__() is not None
- @property
- def string_names(self):
- # For modules
- name = self.py__name__()
- if name is None:
- return ()
- return tuple(name.split('.'))
- def py__file__(self) -> Optional[Path]:
- return self.access_handle.py__file__() # type: ignore[no-any-return]
- class CompiledName(AbstractNameDefinition):
- def __init__(self, inference_state, parent_value, name, is_descriptor):
- self._inference_state = inference_state
- self.parent_context = parent_value.as_context()
- self._parent_value = parent_value
- self.string_name = name
- self.is_descriptor = is_descriptor
- def py__doc__(self):
- return self.infer_compiled_value().py__doc__()
- def _get_qualified_names(self):
- parent_qualified_names = self.parent_context.get_qualified_names()
- if parent_qualified_names is None:
- return None
- return parent_qualified_names + (self.string_name,)
- def get_defining_qualified_value(self):
- context = self.parent_context
- if context.is_module() or context.is_class():
- return self.parent_context.get_value() # Might be None
- return None
- def __repr__(self):
- try:
- name = self.parent_context.name # __name__ is not defined all the time
- except AttributeError:
- name = None
- return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
- @property
- def api_type(self):
- if self.is_descriptor:
- # In case of properties we want to avoid executions as much as
- # possible. Since the api_type can be wrong for other reasons
- # anyway, we just return instance here.
- return "instance"
- return self.infer_compiled_value().api_type
- def infer(self):
- return ValueSet([self.infer_compiled_value()])
- @memoize_method
- def infer_compiled_value(self):
- return create_from_name(self._inference_state, self._parent_value, self.string_name)
- class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
- def __init__(self, compiled_value, signature_param):
- self.parent_context = compiled_value.parent_context
- self._signature_param = signature_param
- @property
- def string_name(self):
- return self._signature_param.name
- def to_string(self):
- s = self._kind_string() + self.string_name
- if self._signature_param.has_annotation:
- s += ': ' + self._signature_param.annotation_string
- if self._signature_param.has_default:
- s += '=' + self._signature_param.default_string
- return s
- def get_kind(self):
- return getattr(Parameter, self._signature_param.kind_name)
- def infer(self):
- p = self._signature_param
- inference_state = self.parent_context.inference_state
- values = NO_VALUES
- if p.has_default:
- values = ValueSet([create_from_access_path(inference_state, p.default)])
- if p.has_annotation:
- annotation = create_from_access_path(inference_state, p.annotation)
- values |= annotation.execute_with_values()
- return values
- class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
- def __init__(self, compiled_value, name, default):
- self.parent_context = compiled_value.parent_context
- self.string_name = name
- self._default = default
- def get_kind(self):
- return Parameter.POSITIONAL_ONLY
- def to_string(self):
- string = self.string_name
- if self._default:
- string += '=' + self._default
- return string
- def infer(self):
- return NO_VALUES
- class CompiledValueName(ValueNameMixin, AbstractNameDefinition):
- def __init__(self, value, name):
- self.string_name = name
- self._value = value
- self.parent_context = value.parent_context
- class EmptyCompiledName(AbstractNameDefinition):
- """
- Accessing some names will raise an exception. To avoid not having any
- completions, just give Jedi the option to return this object. It infers to
- nothing.
- """
- def __init__(self, inference_state, name):
- self.parent_context = inference_state.builtins_module
- self.string_name = name
- def infer(self):
- return NO_VALUES
- class CompiledValueFilter(AbstractFilter):
- def __init__(self, inference_state, compiled_value, is_instance=False):
- self._inference_state = inference_state
- self.compiled_value = compiled_value
- self.is_instance = is_instance
- def get(self, name):
- access_handle = self.compiled_value.access_handle
- safe = not self._inference_state.allow_unsafe_executions
- return self._get(
- name,
- lambda name: access_handle.is_allowed_getattr(name, safe=safe),
- lambda name: name in access_handle.dir(),
- check_has_attribute=True
- )
- def _get(self, name, allowed_getattr_callback, in_dir_callback, check_has_attribute=False):
- """
- To remove quite a few access calls we introduced the callback here.
- """
- has_attribute, is_descriptor, property_return_annotation = allowed_getattr_callback(
- name,
- )
- if property_return_annotation is not None:
- values = create_from_access_path(
- self._inference_state,
- property_return_annotation
- ).execute_annotation()
- if values:
- return [CompiledValueName(v, name) for v in values]
- if check_has_attribute and not has_attribute:
- return []
- if (is_descriptor or not has_attribute) \
- and not self._inference_state.allow_unsafe_executions:
- return [self._get_cached_name(name, is_empty=True)]
- if self.is_instance and not in_dir_callback(name):
- return []
- return [self._get_cached_name(name, is_descriptor=is_descriptor)]
- @memoize_method
- def _get_cached_name(self, name, is_empty=False, *, is_descriptor=False):
- if is_empty:
- return EmptyCompiledName(self._inference_state, name)
- else:
- return self._create_name(name, is_descriptor=is_descriptor)
- def values(self):
- from jedi.inference.compiled import builtin_from_name
- names = []
- needs_type_completions, dir_infos = self.compiled_value.access_handle.get_dir_infos()
- # We could use `safe=False` here as well, especially as a parameter to
- # get_dir_infos. But this would lead to a lot of property executions
- # that are probably not wanted. The drawback for this is that we
- # have a different name for `get` and `values`. For `get` we always
- # execute.
- for name in dir_infos:
- names += self._get(
- name,
- lambda name: dir_infos[name],
- lambda name: name in dir_infos,
- )
- # ``dir`` doesn't include the type names.
- if not self.is_instance and needs_type_completions:
- for filter in builtin_from_name(self._inference_state, 'type').get_filters():
- names += filter.values()
- return names
- def _create_name(self, name, is_descriptor):
- return CompiledName(
- self._inference_state,
- self.compiled_value,
- name,
- is_descriptor,
- )
- def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.compiled_value)
- docstr_defaults = {
- 'floating point number': 'float',
- 'character': 'str',
- 'integer': 'int',
- 'dictionary': 'dict',
- 'string': 'str',
- }
- def _parse_function_doc(doc):
- """
- Takes a function and returns the params and return value as a tuple.
- This is nothing more than a docstring parser.
- TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
- TODO docstrings like 'tuple of integers'
- """
- # parse round parentheses: def func(a, (b,c))
- try:
- count = 0
- start = doc.index('(')
- for i, s in enumerate(doc[start:]):
- if s == '(':
- count += 1
- elif s == ')':
- count -= 1
- if count == 0:
- end = start + i
- break
- param_str = doc[start + 1:end]
- except (ValueError, UnboundLocalError):
- # ValueError for doc.index
- # UnboundLocalError for undefined end in last line
- debug.dbg('no brackets found - no param')
- end = 0
- param_str = ''
- else:
- # remove square brackets, that show an optional param ( = None)
- def change_options(m):
- args = m.group(1).split(',')
- for i, a in enumerate(args):
- if a and '=' not in a:
- args[i] += '=None'
- return ','.join(args)
- while True:
- param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
- change_options, param_str)
- if changes == 0:
- break
- param_str = param_str.replace('-', '_') # see: isinstance.__doc__
- # parse return value
- r = re.search('-[>-]* ', doc[end:end + 7])
- if r is None:
- ret = ''
- else:
- index = end + r.end()
- # get result type, which can contain newlines
- pattern = re.compile(r'(,\n|[^\n-])+')
- ret_str = pattern.match(doc, index).group(0).strip()
- # New object -> object()
- ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
- ret = docstr_defaults.get(ret_str, ret_str)
- return param_str, ret
- def create_from_name(inference_state, compiled_value, name):
- access_paths = compiled_value.access_handle.getattr_paths(name, default=None)
- value = None
- for access_path in access_paths:
- value = create_cached_compiled_value(
- inference_state,
- access_path,
- parent_context=None if value is None else value.as_context(),
- )
- return value
- def _normalize_create_args(func):
- """The cache doesn't care about keyword vs. normal args."""
- def wrapper(inference_state, obj, parent_context=None):
- return func(inference_state, obj, parent_context)
- return wrapper
- def create_from_access_path(inference_state, access_path):
- value = None
- for name, access in access_path.accesses:
- value = create_cached_compiled_value(
- inference_state,
- access,
- parent_context=None if value is None else value.as_context()
- )
- return value
- @_normalize_create_args
- @inference_state_function_cache()
- def create_cached_compiled_value(inference_state, access_handle, parent_context):
- assert not isinstance(parent_context, CompiledValue)
- if parent_context is None:
- cls = CompiledModule
- else:
- cls = CompiledValue
- return cls(inference_state, access_handle, parent_context)
|