mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-22 21:31:26 +08:00
Move some stuff from compiled to context
This commit is contained in:
@@ -1,22 +1,13 @@
|
|||||||
"""
|
import types
|
||||||
Imitate the parser representation.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import types
|
import re
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from jedi._compatibility import builtins as _builtins
|
from jedi._compatibility import builtins as _builtins
|
||||||
|
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
||||||
|
CompiledObjectFilter, CompiledContextName, create_from_access
|
||||||
|
from jedi.evaluate.compiled.access import create_access
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.cache import underscore_memoization, memoize_method
|
|
||||||
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
|
||||||
ContextNameMixin
|
|
||||||
from jedi.evaluate.base_context import Context, ContextSet
|
|
||||||
from jedi.evaluate.lazy_context import LazyKnownContext
|
|
||||||
from jedi.evaluate.compiled.access import _sentinel, create_access
|
|
||||||
from jedi.evaluate.cache import evaluator_function_cache
|
|
||||||
from . import fake
|
|
||||||
|
|
||||||
|
|
||||||
_sep = os.path.sep
|
_sep = os.path.sep
|
||||||
@@ -26,314 +17,66 @@ _path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))
|
|||||||
del _sep
|
del _sep
|
||||||
|
|
||||||
|
|
||||||
class CheckAttribute(object):
|
def builtin_from_name(evaluator, string):
|
||||||
"""Raises an AttributeError if the attribute X isn't available."""
|
bltn_obj = getattr(_builtins, string)
|
||||||
def __init__(self, func):
|
return create(evaluator, bltn_obj)
|
||||||
self.func = func
|
|
||||||
# Remove the py in front of e.g. py__call__.
|
|
||||||
self.check_name = func.__name__[2:]
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
if instance is None:
|
|
||||||
return self
|
|
||||||
|
|
||||||
# This might raise an AttributeError. That's wanted.
|
|
||||||
if self.check_name == '__iter__':
|
|
||||||
# Python iterators are a bit strange, because there's no need for
|
|
||||||
# the __iter__ function as long as __getitem__ is defined (it will
|
|
||||||
# just start with __getitem__(0). This is especially true for
|
|
||||||
# Python 2 strings, where `str.__iter__` is not even defined.
|
|
||||||
if not instance.access.has_iter():
|
|
||||||
raise AttributeError
|
|
||||||
else:
|
|
||||||
instance.access.getattr(self.check_name)
|
|
||||||
return partial(self.func, instance)
|
|
||||||
|
|
||||||
|
|
||||||
class CompiledObject(Context):
|
def create(evaluator, obj):
|
||||||
def __init__(self, evaluator, access, parent_context=None, faked_class=None):
|
return create_from_access(
|
||||||
super(CompiledObject, self).__init__(evaluator, parent_context)
|
evaluator, create_access(evaluator, obj)
|
||||||
self.access = access
|
)
|
||||||
# This attribute will not be set for most classes, except for fakes.
|
|
||||||
self.tree_node = faked_class
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__call__(self, params):
|
|
||||||
if self.tree_node is not None and self.tree_node.type == 'funcdef':
|
|
||||||
from jedi.evaluate.context.function import FunctionContext
|
|
||||||
return FunctionContext(
|
|
||||||
self.evaluator,
|
|
||||||
parent_context=self.parent_context,
|
|
||||||
funcdef=self.tree_node
|
|
||||||
).py__call__(params)
|
|
||||||
if self.access.is_class():
|
|
||||||
from jedi.evaluate.context import CompiledInstance
|
|
||||||
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
|
|
||||||
else:
|
|
||||||
return ContextSet.from_iterable(self._execute_function(params))
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__class__(self):
|
|
||||||
return _create_from_access(self.evaluator, self.access.py__class__())
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__mro__(self):
|
|
||||||
return (self,) + tuple(
|
|
||||||
_create_from_access(self.evaluator, access) for access in self.access.py__mro__accesses()
|
|
||||||
)
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__bases__(self):
|
|
||||||
return tuple(
|
|
||||||
_create_from_access(self.evaluator, access)
|
|
||||||
for access in self.access.py__bases__()
|
|
||||||
)
|
|
||||||
|
|
||||||
def py__bool__(self):
|
|
||||||
return self.access.py__bool__()
|
|
||||||
|
|
||||||
def py__file__(self):
|
|
||||||
return self.access.py__file__()
|
|
||||||
|
|
||||||
def is_class(self):
|
|
||||||
return self.access.is_class()
|
|
||||||
|
|
||||||
def py__doc__(self, include_call_signature=False):
|
|
||||||
return self.access.py__doc__()
|
|
||||||
|
|
||||||
def get_param_names(self):
|
|
||||||
try:
|
|
||||||
signature_params = self.access.get_signature_params()
|
|
||||||
except ValueError: # Has no signature
|
|
||||||
params_str, ret = self._parse_function_doc()
|
|
||||||
tokens = params_str.split(',')
|
|
||||||
if self.access.ismethoddescriptor():
|
|
||||||
tokens.insert(0, 'self')
|
|
||||||
for p in tokens:
|
|
||||||
parts = p.strip().split('=')
|
|
||||||
yield UnresolvableParamName(self, parts[0])
|
|
||||||
else:
|
|
||||||
for signature_param in signature_params:
|
|
||||||
yield SignatureParamName(self, signature_param)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.access.get_repr())
|
|
||||||
|
|
||||||
@underscore_memoization
|
|
||||||
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.get_api_type()
|
|
||||||
|
|
||||||
@underscore_memoization
|
|
||||||
def _cls(self):
|
|
||||||
"""
|
|
||||||
We used to limit the lookups for instantiated objects like list(), but
|
|
||||||
this is not the case anymore. Python itself
|
|
||||||
"""
|
|
||||||
# Ensures that a CompiledObject is returned that is not an instance (like list)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_filters(self, search_global=False, is_instance=False,
|
|
||||||
until_position=None, origin_scope=None):
|
|
||||||
yield self._ensure_one_filter(is_instance)
|
|
||||||
|
|
||||||
@memoize_method
|
|
||||||
def _ensure_one_filter(self, is_instance):
|
|
||||||
"""
|
|
||||||
search_global shouldn't change the fact that there's one dict, this way
|
|
||||||
there's only one `object`.
|
|
||||||
"""
|
|
||||||
return CompiledObjectFilter(self.evaluator, self, is_instance)
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__getitem__(self, index):
|
|
||||||
access = self.access.py__getitem__(index)
|
|
||||||
if access is None:
|
|
||||||
return ContextSet()
|
|
||||||
|
|
||||||
return ContextSet(_create_from_access(self.evaluator, access))
|
|
||||||
|
|
||||||
@CheckAttribute
|
|
||||||
def py__iter__(self):
|
|
||||||
for access in self.access.py__iter__list():
|
|
||||||
yield LazyKnownContext(_create_from_access(self.evaluator, access))
|
|
||||||
|
|
||||||
def py__name__(self):
|
|
||||||
return self.access.py__name__()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
name = self.py__name__()
|
|
||||||
if name is None:
|
|
||||||
name = self.access.get_repr()
|
|
||||||
return CompiledContextName(self, name)
|
|
||||||
|
|
||||||
def _execute_function(self, params):
|
|
||||||
from jedi.evaluate import docstrings
|
|
||||||
if self.api_type != 'function':
|
|
||||||
return
|
|
||||||
for name in self._parse_function_doc()[1].split():
|
|
||||||
try:
|
|
||||||
bltn_obj = getattr(_builtins, name)
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if bltn_obj is None:
|
|
||||||
# We want to evaluate everything except None.
|
|
||||||
# TODO do we?
|
|
||||||
continue
|
|
||||||
bltn_obj = create(self.evaluator, bltn_obj)
|
|
||||||
for result in bltn_obj.execute(params):
|
|
||||||
yield result
|
|
||||||
for type_ in docstrings.infer_return_types(self):
|
|
||||||
yield type_
|
|
||||||
|
|
||||||
def dict_values(self):
|
|
||||||
return ContextSet.from_iterable(
|
|
||||||
_create_from_access(self.evaluator, access) for access in self.access.dict_values()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_safe_value(self, default=_sentinel):
|
|
||||||
try:
|
|
||||||
return self.access.get_safe_value()
|
|
||||||
except ValueError:
|
|
||||||
if default == _sentinel:
|
|
||||||
raise
|
|
||||||
return default
|
|
||||||
|
|
||||||
def execute_operation(self, other, operator):
|
|
||||||
return _create_from_access(
|
|
||||||
self.evaluator,
|
|
||||||
self.access.execute_operation(other.access, operator)
|
|
||||||
)
|
|
||||||
|
|
||||||
def negate(self):
|
|
||||||
return _create_from_access(self.evaluator, self.access.negate())
|
|
||||||
|
|
||||||
def is_super_class(self, exception):
|
|
||||||
return self.access.is_super_class(exception)
|
|
||||||
|
|
||||||
|
|
||||||
class CompiledName(AbstractNameDefinition):
|
def _a_generator(foo):
|
||||||
def __init__(self, evaluator, parent_context, name):
|
"""Used to have an object to return for generators."""
|
||||||
self._evaluator = evaluator
|
yield 42
|
||||||
self.parent_context = parent_context
|
yield foo
|
||||||
self.string_name = name
|
|
||||||
|
|
||||||
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):
|
|
||||||
return next(iter(self.infer())).api_type
|
|
||||||
|
|
||||||
@underscore_memoization
|
|
||||||
def infer(self):
|
|
||||||
return ContextSet(_create_from_name(
|
|
||||||
self._evaluator, self.parent_context, self.string_name
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureParamName(AbstractNameDefinition):
|
_SPECIAL_OBJECTS = {
|
||||||
api_type = 'param'
|
'FUNCTION_CLASS': types.FunctionType,
|
||||||
|
'METHOD_CLASS': type(CompiledObject.is_class),
|
||||||
def __init__(self, compiled_obj, signature_param):
|
'MODULE_CLASS': types.ModuleType,
|
||||||
self.parent_context = compiled_obj.parent_context
|
'GENERATOR_OBJECT': _a_generator(1.0),
|
||||||
self._signature_param = signature_param
|
'BUILTINS': _builtins,
|
||||||
|
}
|
||||||
@property
|
|
||||||
def string_name(self):
|
|
||||||
return self._signature_param.name
|
|
||||||
|
|
||||||
def infer(self):
|
|
||||||
p = self._signature_param
|
|
||||||
evaluator = self.parent_context.evaluator
|
|
||||||
contexts = ContextSet()
|
|
||||||
if p.has_default:
|
|
||||||
contexts = ContextSet(_create_from_access(evaluator, p.default))
|
|
||||||
if p.has_annotation:
|
|
||||||
annotation = _create_from_access(evaluator, p.annotation)
|
|
||||||
contexts |= annotation.execute_evaluated()
|
|
||||||
return contexts
|
|
||||||
|
|
||||||
|
|
||||||
class UnresolvableParamName(AbstractNameDefinition):
|
def get_special_object(evaluator, identifier):
|
||||||
api_type = 'param'
|
obj = _SPECIAL_OBJECTS[identifier]
|
||||||
|
return create(evaluator, obj)
|
||||||
def __init__(self, compiled_obj, name):
|
|
||||||
self.parent_context = compiled_obj.parent_context
|
|
||||||
self.string_name = name
|
|
||||||
|
|
||||||
def infer(self):
|
|
||||||
return ContextSet()
|
|
||||||
|
|
||||||
|
|
||||||
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
|
def load_module(evaluator, path=None, name=None):
|
||||||
def __init__(self, context, name):
|
sys_path = list(evaluator.project.sys_path)
|
||||||
self.string_name = name
|
if path is not None:
|
||||||
self._context = context
|
dotted_path = dotted_from_fs_path(path, sys_path=sys_path)
|
||||||
self.parent_context = context.parent_context
|
else:
|
||||||
|
dotted_path = name
|
||||||
|
|
||||||
|
temp, sys.path = sys.path, sys_path
|
||||||
|
try:
|
||||||
|
__import__(dotted_path)
|
||||||
|
except RuntimeError:
|
||||||
|
if 'PySide' in dotted_path or 'PyQt' in dotted_path:
|
||||||
|
# RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap
|
||||||
|
# the QObject class.
|
||||||
|
# See https://github.com/davidhalter/jedi/pull/483
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
except ImportError:
|
||||||
|
# If a module is "corrupt" or not really a Python module or whatever.
|
||||||
|
debug.warning('Module %s not importable in path %s.', dotted_path, path)
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
sys.path = temp
|
||||||
|
|
||||||
class EmptyCompiledName(AbstractNameDefinition):
|
# Just access the cache after import, because of #59 as well as the very
|
||||||
"""
|
# complicated import structure of Python.
|
||||||
Accessing some names will raise an exception. To avoid not having any
|
module = sys.modules[dotted_path]
|
||||||
completions, just give Jedi the option to return this object. It infers to
|
|
||||||
nothing.
|
|
||||||
"""
|
|
||||||
def __init__(self, evaluator, name):
|
|
||||||
self.parent_context = evaluator.BUILTINS
|
|
||||||
self.string_name = name
|
|
||||||
|
|
||||||
def infer(self):
|
return create(evaluator, module)
|
||||||
return ContextSet()
|
|
||||||
|
|
||||||
|
|
||||||
class CompiledObjectFilter(AbstractFilter):
|
|
||||||
name_class = CompiledName
|
|
||||||
|
|
||||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
|
||||||
self._evaluator = evaluator
|
|
||||||
self._compiled_object = compiled_object
|
|
||||||
self._is_instance = is_instance
|
|
||||||
|
|
||||||
@memoize_method
|
|
||||||
def get(self, name):
|
|
||||||
name = str(name)
|
|
||||||
try:
|
|
||||||
if not self._compiled_object.access.is_allowed_getattr(name):
|
|
||||||
return [EmptyCompiledName(self._evaluator, name)]
|
|
||||||
except AttributeError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if self._is_instance and name not in self._compiled_object.access.dir():
|
|
||||||
return []
|
|
||||||
return [self._create_name(name)]
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
names = []
|
|
||||||
for name in self._compiled_object.access.dir():
|
|
||||||
names += self.get(name)
|
|
||||||
|
|
||||||
# ``dir`` doesn't include the type names.
|
|
||||||
if not self._is_instance and self._compiled_object.access.needs_type_completions():
|
|
||||||
for filter in create(self._evaluator, type).get_filters():
|
|
||||||
names += filter.values()
|
|
||||||
return names
|
|
||||||
|
|
||||||
def _create_name(self, name):
|
|
||||||
return self.name_class(self._evaluator, self._compiled_object, name)
|
|
||||||
|
|
||||||
|
|
||||||
def dotted_from_fs_path(fs_path, sys_path):
|
def dotted_from_fs_path(fs_path, sys_path):
|
||||||
@@ -369,184 +112,3 @@ def dotted_from_fs_path(fs_path, sys_path):
|
|||||||
# - Window
|
# - Window
|
||||||
# Replace like X:\path\to\something/foo/bar.py
|
# Replace like X:\path\to\something/foo/bar.py
|
||||||
return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.')
|
return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.')
|
||||||
|
|
||||||
|
|
||||||
def load_module(evaluator, path=None, name=None):
|
|
||||||
sys_path = list(evaluator.project.sys_path)
|
|
||||||
if path is not None:
|
|
||||||
dotted_path = dotted_from_fs_path(path, sys_path=sys_path)
|
|
||||||
else:
|
|
||||||
dotted_path = name
|
|
||||||
|
|
||||||
temp, sys.path = sys.path, sys_path
|
|
||||||
try:
|
|
||||||
__import__(dotted_path)
|
|
||||||
except RuntimeError:
|
|
||||||
if 'PySide' in dotted_path or 'PyQt' in dotted_path:
|
|
||||||
# RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap
|
|
||||||
# the QObject class.
|
|
||||||
# See https://github.com/davidhalter/jedi/pull/483
|
|
||||||
return None
|
|
||||||
raise
|
|
||||||
except ImportError:
|
|
||||||
# If a module is "corrupt" or not really a Python module or whatever.
|
|
||||||
debug.warning('Module %s not importable in path %s.', dotted_path, path)
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
sys.path = temp
|
|
||||||
|
|
||||||
# Just access the cache after import, because of #59 as well as the very
|
|
||||||
# complicated import structure of Python.
|
|
||||||
module = sys.modules[dotted_path]
|
|
||||||
|
|
||||||
return create(evaluator, module)
|
|
||||||
|
|
||||||
|
|
||||||
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(evaluator, compiled_object, name):
|
|
||||||
faked = None
|
|
||||||
try:
|
|
||||||
faked = fake.get_faked_with_parent_context(compiled_object, name)
|
|
||||||
except fake.FakeDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
access = compiled_object.access.getattr(name, default=None)
|
|
||||||
return create_cached_compiled_object(
|
|
||||||
evaluator, access, parent_context=compiled_object, faked=faked
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def builtin_from_name(evaluator, string):
|
|
||||||
bltn_obj = getattr(_builtins, string)
|
|
||||||
return create(evaluator, bltn_obj)
|
|
||||||
|
|
||||||
|
|
||||||
def _a_generator(foo):
|
|
||||||
"""Used to have an object to return for generators."""
|
|
||||||
yield 42
|
|
||||||
yield foo
|
|
||||||
|
|
||||||
|
|
||||||
_SPECIAL_OBJECTS = {
|
|
||||||
'FUNCTION_CLASS': types.FunctionType,
|
|
||||||
'METHOD_CLASS': type(CompiledObject.is_class),
|
|
||||||
'MODULE_CLASS': types.ModuleType,
|
|
||||||
'GENERATOR_OBJECT': _a_generator(1.0),
|
|
||||||
'BUILTINS': _builtins,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_special_object(evaluator, identifier):
|
|
||||||
obj = _SPECIAL_OBJECTS[identifier]
|
|
||||||
return create(evaluator, obj)
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_create_args(func):
|
|
||||||
"""The cache doesn't care about keyword vs. normal args."""
|
|
||||||
def wrapper(evaluator, obj, parent_context=None, faked=None):
|
|
||||||
return func(evaluator, obj, parent_context, faked)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def create(evaluator, obj):
|
|
||||||
return _create_from_access(
|
|
||||||
evaluator, create_access(evaluator, obj)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@evaluator_function_cache()
|
|
||||||
def _create_from_access(evaluator, access):
|
|
||||||
"""
|
|
||||||
Returns a CompiledObject and tries to find fake modules.
|
|
||||||
"""
|
|
||||||
access_tuples = access.get_access_path_tuples()
|
|
||||||
if access_tuples:
|
|
||||||
string_names, accesses = zip(*access_tuples)
|
|
||||||
try:
|
|
||||||
tree_nodes = fake.get_faked_tree_nodes(evaluator.latest_grammar, string_names)
|
|
||||||
except fake.FakeDoesNotExist:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
parent_context = None
|
|
||||||
for access2, tree_node in zip(accesses, tree_nodes):
|
|
||||||
parent_context = create_cached_compiled_object(
|
|
||||||
evaluator, access2, parent_context, faked=tree_node
|
|
||||||
)
|
|
||||||
return parent_context
|
|
||||||
|
|
||||||
parent_context = create(evaluator, _builtins)
|
|
||||||
return create_cached_compiled_object(evaluator, access, parent_context)
|
|
||||||
|
|
||||||
|
|
||||||
@_normalize_create_args
|
|
||||||
@evaluator_function_cache()
|
|
||||||
def create_cached_compiled_object(evaluator, access, parent_context, faked):
|
|
||||||
return CompiledObject(evaluator, access, parent_context, faked)
|
|
||||||
|
|||||||
447
jedi/evaluate/compiled/context.py
Normal file
447
jedi/evaluate/compiled/context.py
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
"""
|
||||||
|
Imitate the parser representation.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from jedi import debug
|
||||||
|
from jedi.cache import underscore_memoization, memoize_method
|
||||||
|
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
||||||
|
ContextNameMixin
|
||||||
|
from jedi.evaluate.base_context import Context, ContextSet
|
||||||
|
from jedi.evaluate.lazy_context import LazyKnownContext
|
||||||
|
from jedi.evaluate.compiled.access import _sentinel
|
||||||
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
|
from . import fake
|
||||||
|
|
||||||
|
|
||||||
|
class CheckAttribute(object):
|
||||||
|
"""Raises an AttributeError if the attribute X isn't available."""
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
# Remove the py in front of e.g. py__call__.
|
||||||
|
self.check_name = func.__name__[2:]
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
|
||||||
|
# This might raise an AttributeError. That's wanted.
|
||||||
|
if self.check_name == '__iter__':
|
||||||
|
# Python iterators are a bit strange, because there's no need for
|
||||||
|
# the __iter__ function as long as __getitem__ is defined (it will
|
||||||
|
# just start with __getitem__(0). This is especially true for
|
||||||
|
# Python 2 strings, where `str.__iter__` is not even defined.
|
||||||
|
if not instance.access.has_iter():
|
||||||
|
raise AttributeError
|
||||||
|
else:
|
||||||
|
instance.access.getattr(self.check_name)
|
||||||
|
return partial(self.func, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class CompiledObject(Context):
|
||||||
|
def __init__(self, evaluator, access, parent_context=None, faked_class=None):
|
||||||
|
super(CompiledObject, self).__init__(evaluator, parent_context)
|
||||||
|
self.access = access
|
||||||
|
# This attribute will not be set for most classes, except for fakes.
|
||||||
|
self.tree_node = faked_class
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__call__(self, params):
|
||||||
|
if self.tree_node is not None and self.tree_node.type == 'funcdef':
|
||||||
|
from jedi.evaluate.context.function import FunctionContext
|
||||||
|
return FunctionContext(
|
||||||
|
self.evaluator,
|
||||||
|
parent_context=self.parent_context,
|
||||||
|
funcdef=self.tree_node
|
||||||
|
).py__call__(params)
|
||||||
|
if self.access.is_class():
|
||||||
|
from jedi.evaluate.context import CompiledInstance
|
||||||
|
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
|
||||||
|
else:
|
||||||
|
return ContextSet.from_iterable(self._execute_function(params))
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__class__(self):
|
||||||
|
return create_from_access(self.evaluator, self.access.py__class__())
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__mro__(self):
|
||||||
|
return (self,) + tuple(
|
||||||
|
create_from_access(self.evaluator, access) for access in self.access.py__mro__accesses()
|
||||||
|
)
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__bases__(self):
|
||||||
|
return tuple(
|
||||||
|
create_from_access(self.evaluator, access)
|
||||||
|
for access in self.access.py__bases__()
|
||||||
|
)
|
||||||
|
|
||||||
|
def py__bool__(self):
|
||||||
|
return self.access.py__bool__()
|
||||||
|
|
||||||
|
def py__file__(self):
|
||||||
|
return self.access.py__file__()
|
||||||
|
|
||||||
|
def is_class(self):
|
||||||
|
return self.access.is_class()
|
||||||
|
|
||||||
|
def py__doc__(self, include_call_signature=False):
|
||||||
|
return self.access.py__doc__()
|
||||||
|
|
||||||
|
def get_param_names(self):
|
||||||
|
try:
|
||||||
|
signature_params = self.access.get_signature_params()
|
||||||
|
except ValueError: # Has no signature
|
||||||
|
params_str, ret = self._parse_function_doc()
|
||||||
|
tokens = params_str.split(',')
|
||||||
|
if self.access.ismethoddescriptor():
|
||||||
|
tokens.insert(0, 'self')
|
||||||
|
for p in tokens:
|
||||||
|
parts = p.strip().split('=')
|
||||||
|
yield UnresolvableParamName(self, parts[0])
|
||||||
|
else:
|
||||||
|
for signature_param in signature_params:
|
||||||
|
yield SignatureParamName(self, signature_param)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self.access.get_repr())
|
||||||
|
|
||||||
|
@underscore_memoization
|
||||||
|
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.get_api_type()
|
||||||
|
|
||||||
|
@underscore_memoization
|
||||||
|
def _cls(self):
|
||||||
|
"""
|
||||||
|
We used to limit the lookups for instantiated objects like list(), but
|
||||||
|
this is not the case anymore. Python itself
|
||||||
|
"""
|
||||||
|
# Ensures that a CompiledObject is returned that is not an instance (like list)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_filters(self, search_global=False, is_instance=False,
|
||||||
|
until_position=None, origin_scope=None):
|
||||||
|
yield self._ensure_one_filter(is_instance)
|
||||||
|
|
||||||
|
@memoize_method
|
||||||
|
def _ensure_one_filter(self, is_instance):
|
||||||
|
"""
|
||||||
|
search_global shouldn't change the fact that there's one dict, this way
|
||||||
|
there's only one `object`.
|
||||||
|
"""
|
||||||
|
return CompiledObjectFilter(self.evaluator, self, is_instance)
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__getitem__(self, index):
|
||||||
|
access = self.access.py__getitem__(index)
|
||||||
|
if access is None:
|
||||||
|
return ContextSet()
|
||||||
|
|
||||||
|
return ContextSet(create_from_access(self.evaluator, access))
|
||||||
|
|
||||||
|
@CheckAttribute
|
||||||
|
def py__iter__(self):
|
||||||
|
for access in self.access.py__iter__list():
|
||||||
|
yield LazyKnownContext(create_from_access(self.evaluator, access))
|
||||||
|
|
||||||
|
def py__name__(self):
|
||||||
|
return self.access.py__name__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
name = self.py__name__()
|
||||||
|
if name is None:
|
||||||
|
name = self.access.get_repr()
|
||||||
|
return CompiledContextName(self, name)
|
||||||
|
|
||||||
|
def _execute_function(self, params):
|
||||||
|
from jedi.evaluate import docstrings
|
||||||
|
from jedi.evaluate.compiled import create, _builtins
|
||||||
|
if self.api_type != 'function':
|
||||||
|
return
|
||||||
|
for name in self._parse_function_doc()[1].split():
|
||||||
|
try:
|
||||||
|
bltn_obj = getattr(_builtins, name)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if bltn_obj is None:
|
||||||
|
# We want to evaluate everything except None.
|
||||||
|
# TODO do we?
|
||||||
|
continue
|
||||||
|
bltn_obj = create(self.evaluator, bltn_obj)
|
||||||
|
for result in bltn_obj.execute(params):
|
||||||
|
yield result
|
||||||
|
for type_ in docstrings.infer_return_types(self):
|
||||||
|
yield type_
|
||||||
|
|
||||||
|
def dict_values(self):
|
||||||
|
return ContextSet.from_iterable(
|
||||||
|
create_from_access(self.evaluator, access) for access in self.access.dict_values()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_safe_value(self, default=_sentinel):
|
||||||
|
try:
|
||||||
|
return self.access.get_safe_value()
|
||||||
|
except ValueError:
|
||||||
|
if default == _sentinel:
|
||||||
|
raise
|
||||||
|
return default
|
||||||
|
|
||||||
|
def execute_operation(self, other, operator):
|
||||||
|
return create_from_access(
|
||||||
|
self.evaluator,
|
||||||
|
self.access.execute_operation(other.access, operator)
|
||||||
|
)
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return create_from_access(self.evaluator, self.access.negate())
|
||||||
|
|
||||||
|
def is_super_class(self, exception):
|
||||||
|
return self.access.is_super_class(exception)
|
||||||
|
|
||||||
|
|
||||||
|
class CompiledName(AbstractNameDefinition):
|
||||||
|
def __init__(self, evaluator, parent_context, name):
|
||||||
|
self._evaluator = evaluator
|
||||||
|
self.parent_context = parent_context
|
||||||
|
self.string_name = name
|
||||||
|
|
||||||
|
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):
|
||||||
|
return next(iter(self.infer())).api_type
|
||||||
|
|
||||||
|
@underscore_memoization
|
||||||
|
def infer(self):
|
||||||
|
return ContextSet(_create_from_name(
|
||||||
|
self._evaluator, self.parent_context, self.string_name
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureParamName(AbstractNameDefinition):
|
||||||
|
api_type = 'param'
|
||||||
|
|
||||||
|
def __init__(self, compiled_obj, signature_param):
|
||||||
|
self.parent_context = compiled_obj.parent_context
|
||||||
|
self._signature_param = signature_param
|
||||||
|
|
||||||
|
@property
|
||||||
|
def string_name(self):
|
||||||
|
return self._signature_param.name
|
||||||
|
|
||||||
|
def infer(self):
|
||||||
|
p = self._signature_param
|
||||||
|
evaluator = self.parent_context.evaluator
|
||||||
|
contexts = ContextSet()
|
||||||
|
if p.has_default:
|
||||||
|
contexts = ContextSet(create_from_access(evaluator, p.default))
|
||||||
|
if p.has_annotation:
|
||||||
|
annotation = create_from_access(evaluator, p.annotation)
|
||||||
|
contexts |= annotation.execute_evaluated()
|
||||||
|
return contexts
|
||||||
|
|
||||||
|
|
||||||
|
class UnresolvableParamName(AbstractNameDefinition):
|
||||||
|
api_type = 'param'
|
||||||
|
|
||||||
|
def __init__(self, compiled_obj, name):
|
||||||
|
self.parent_context = compiled_obj.parent_context
|
||||||
|
self.string_name = name
|
||||||
|
|
||||||
|
def infer(self):
|
||||||
|
return ContextSet()
|
||||||
|
|
||||||
|
|
||||||
|
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
|
||||||
|
def __init__(self, context, name):
|
||||||
|
self.string_name = name
|
||||||
|
self._context = context
|
||||||
|
self.parent_context = context.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, evaluator, name):
|
||||||
|
self.parent_context = evaluator.BUILTINS
|
||||||
|
self.string_name = name
|
||||||
|
|
||||||
|
def infer(self):
|
||||||
|
return ContextSet()
|
||||||
|
|
||||||
|
|
||||||
|
class CompiledObjectFilter(AbstractFilter):
|
||||||
|
name_class = CompiledName
|
||||||
|
|
||||||
|
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||||
|
self._evaluator = evaluator
|
||||||
|
self._compiled_object = compiled_object
|
||||||
|
self._is_instance = is_instance
|
||||||
|
|
||||||
|
@memoize_method
|
||||||
|
def get(self, name):
|
||||||
|
name = str(name)
|
||||||
|
try:
|
||||||
|
if not self._compiled_object.access.is_allowed_getattr(name):
|
||||||
|
return [EmptyCompiledName(self._evaluator, name)]
|
||||||
|
except AttributeError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if self._is_instance and name not in self._compiled_object.access.dir():
|
||||||
|
return []
|
||||||
|
return [self._create_name(name)]
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
names = []
|
||||||
|
for name in self._compiled_object.access.dir():
|
||||||
|
names += self.get(name)
|
||||||
|
|
||||||
|
# ``dir`` doesn't include the type names.
|
||||||
|
from jedi.evaluate.compiled import create
|
||||||
|
if not self._is_instance and self._compiled_object.access.needs_type_completions():
|
||||||
|
for filter in create(self._evaluator, type).get_filters():
|
||||||
|
names += filter.values()
|
||||||
|
return names
|
||||||
|
|
||||||
|
def _create_name(self, name):
|
||||||
|
return self.name_class(self._evaluator, self._compiled_object, name)
|
||||||
|
|
||||||
|
|
||||||
|
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(evaluator, compiled_object, name):
|
||||||
|
faked = None
|
||||||
|
try:
|
||||||
|
faked = fake.get_faked_with_parent_context(compiled_object, name)
|
||||||
|
except fake.FakeDoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
access = compiled_object.access.getattr(name, default=None)
|
||||||
|
return create_cached_compiled_object(
|
||||||
|
evaluator, access, parent_context=compiled_object, faked=faked
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_create_args(func):
|
||||||
|
"""The cache doesn't care about keyword vs. normal args."""
|
||||||
|
def wrapper(evaluator, obj, parent_context=None, faked=None):
|
||||||
|
return func(evaluator, obj, parent_context, faked)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@evaluator_function_cache()
|
||||||
|
def create_from_access(evaluator, access):
|
||||||
|
"""
|
||||||
|
Returns a CompiledObject and tries to find fake modules.
|
||||||
|
"""
|
||||||
|
access_tuples = access.get_access_path_tuples()
|
||||||
|
if access_tuples:
|
||||||
|
string_names, accesses = zip(*access_tuples)
|
||||||
|
try:
|
||||||
|
tree_nodes = fake.get_faked_tree_nodes(evaluator.latest_grammar, string_names)
|
||||||
|
except fake.FakeDoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
parent_context = None
|
||||||
|
for access2, tree_node in zip(accesses, tree_nodes):
|
||||||
|
parent_context = create_cached_compiled_object(
|
||||||
|
evaluator, access2, parent_context, faked=tree_node
|
||||||
|
)
|
||||||
|
return parent_context
|
||||||
|
|
||||||
|
from jedi.evaluate.compiled import create, _builtins
|
||||||
|
parent_context = create(evaluator, _builtins)
|
||||||
|
return create_cached_compiled_object(evaluator, access, parent_context)
|
||||||
|
|
||||||
|
|
||||||
|
@_normalize_create_args
|
||||||
|
@evaluator_function_cache()
|
||||||
|
def create_cached_compiled_object(evaluator, access, parent_context, faked):
|
||||||
|
return CompiledObject(evaluator, access, parent_context, faked)
|
||||||
@@ -14,6 +14,7 @@ from jedi.evaluate.context import ModuleContext
|
|||||||
from jedi.evaluate.cache import evaluator_function_cache
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||||
from jedi.evaluate.compiled.access import compiled_objects_cache
|
from jedi.evaluate.compiled.access import compiled_objects_cache
|
||||||
|
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
||||||
|
|
||||||
|
|
||||||
class MixedObject(object):
|
class MixedObject(object):
|
||||||
@@ -197,7 +198,7 @@ def _find_syntax_node_name(evaluator, access):
|
|||||||
def _create(evaluator, access, parent_context=None, *args):
|
def _create(evaluator, access, parent_context=None, *args):
|
||||||
tree_node, path = _find_syntax_node_name(evaluator, access)
|
tree_node, path = _find_syntax_node_name(evaluator, access)
|
||||||
|
|
||||||
compiled_object = compiled.create_cached_compiled_object(
|
compiled_object = create_cached_compiled_object(
|
||||||
evaluator, access, parent_context=parent_context.compiled_object)
|
evaluator, access, parent_context=parent_context.compiled_object)
|
||||||
if tree_node is None:
|
if tree_node is None:
|
||||||
return compiled_object
|
return compiled_object
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def test_simple(evaluator):
|
|||||||
def test_fake_loading(evaluator):
|
def test_fake_loading(evaluator):
|
||||||
builtin = compiled.get_special_object(evaluator, 'BUILTINS')
|
builtin = compiled.get_special_object(evaluator, 'BUILTINS')
|
||||||
string, = builtin.py__getattribute__('str')
|
string, = builtin.py__getattribute__('str')
|
||||||
from_name = compiled._create_from_name(evaluator, string, '__init__')
|
from_name = compiled.context._create_from_name(evaluator, string, '__init__')
|
||||||
assert from_name.tree_node
|
assert from_name.tree_node
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ def test_parse_function_doc_illegal_docstr():
|
|||||||
|
|
||||||
doesn't have a closing bracket.
|
doesn't have a closing bracket.
|
||||||
"""
|
"""
|
||||||
assert ('', '') == compiled._parse_function_doc(docstr)
|
assert ('', '') == compiled.context._parse_function_doc(docstr)
|
||||||
|
|
||||||
|
|
||||||
def test_doc(evaluator):
|
def test_doc(evaluator):
|
||||||
|
|||||||
Reference in New Issue
Block a user