Add an access abstraction (only array tests work, yet)

The access abstraction will be the new way of accessing builtin objects. This way it will be easier to move that to another process
This commit is contained in:
Dave Halter
2017-11-25 19:47:49 +01:00
parent 52bc1be84e
commit 7263d8565b
10 changed files with 408 additions and 235 deletions

View File

@@ -16,7 +16,7 @@ import parso
from parso.python import tree from parso.python import tree
from parso import python_bytes_to_unicode, split_lines from parso import python_bytes_to_unicode, split_lines
from jedi.parser_utils import get_executable_nodes, get_statement_of_position from jedi.parser_utils import get_executable_nodes
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi import cache from jedi import cache

View File

@@ -85,8 +85,13 @@ class Context(BaseContext):
result = ContextSet() result = ContextSet()
for index in index_contexts: for index in index_contexts:
if isinstance(index, (CompiledObject, Slice)): if isinstance(index, Slice):
index = index.obj index = index.obj
if isinstance(index, CompiledObject):
try:
index = index.get_safe_value()
except ValueError:
pass
if type(index) not in (float, int, str, unicode, slice, type(Ellipsis)): if type(index) not in (float, int, str, unicode, slice, type(Ellipsis)):
# If the index is not clearly defined, we have to get all the # If the index is not clearly defined, we have to get all the

View File

@@ -16,6 +16,7 @@ from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
from jedi.evaluate.base_context import Context, ContextSet from jedi.evaluate.base_context import Context, ContextSet
from jedi.evaluate.lazy_context import LazyKnownContext from jedi.evaluate.lazy_context import LazyKnownContext
from jedi.evaluate.compiled.getattr_static import getattr_static from jedi.evaluate.compiled.getattr_static import getattr_static
from jedi.evaluate.compiled.access import DirectObjectAccess, _sentinel
from . import fake from . import fake
@@ -25,23 +26,6 @@ if os.path.altsep is not None:
_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) _path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
del _sep del _sep
# Those types don't exist in typing.
MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__)
# `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object)
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
ALLOWED_DESCRIPTOR_ACCESS = (
types.FunctionType,
types.GetSetDescriptorType,
types.MemberDescriptorType,
MethodDescriptorType,
WrapperDescriptorType,
ClassMethodDescriptorType,
staticmethod,
classmethod,
)
class CheckAttribute(object): class CheckAttribute(object):
"""Raises an AttributeError if the attribute X isn't available.""" """Raises an AttributeError if the attribute X isn't available."""
@@ -60,12 +44,10 @@ class CheckAttribute(object):
# the __iter__ function as long as __getitem__ is defined (it will # the __iter__ function as long as __getitem__ is defined (it will
# just start with __getitem__(0). This is especially true for # just start with __getitem__(0). This is especially true for
# Python 2 strings, where `str.__iter__` is not even defined. # Python 2 strings, where `str.__iter__` is not even defined.
try: if not instance.access.has_iter():
iter(instance.obj)
except TypeError:
raise AttributeError raise AttributeError
else: else:
getattr(instance.obj, self.check_name) instance.access.getattr(self.check_name)
return partial(self.func, instance) return partial(self.func, instance)
@@ -73,9 +55,9 @@ class CompiledObject(Context):
path = None # modules have this attribute - set it to None. path = None # modules have this attribute - set it to None.
used_names = lambda self: {} # To be consistent with modules. used_names = lambda self: {} # To be consistent with modules.
def __init__(self, evaluator, obj, parent_context=None, faked_class=None): def __init__(self, evaluator, access, parent_context=None, faked_class=None):
super(CompiledObject, self).__init__(evaluator, parent_context) super(CompiledObject, self).__init__(evaluator, parent_context)
self.obj = obj self.access = access
# This attribute will not be set for most classes, except for fakes. # This attribute will not be set for most classes, except for fakes.
self.tree_node = faked_class self.tree_node = faked_class
@@ -85,7 +67,7 @@ class CompiledObject(Context):
@CheckAttribute @CheckAttribute
def py__call__(self, params): def py__call__(self, params):
if inspect.isclass(self.obj): if self.access.is_class():
from jedi.evaluate.context import CompiledInstance from jedi.evaluate.context import CompiledInstance
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params)) return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
else: else:
@@ -97,26 +79,25 @@ class CompiledObject(Context):
@CheckAttribute @CheckAttribute
def py__mro__(self): def py__mro__(self):
return (self,) + tuple(create(self.evaluator, cls) for cls in self.obj.__mro__[1:]) return (self,) + tuple(
_create_from_access(self.evaluator, access) for access in self.access.py__mro__accesses()
)
@CheckAttribute @CheckAttribute
def py__bases__(self): def py__bases__(self):
return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__) return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__)
def py__bool__(self): def py__bool__(self):
return bool(self.obj) return self.access.py__bool__()
def py__file__(self): def py__file__(self):
try: return self.access.py__file__()
return self.obj.__file__
except AttributeError:
return None
def is_class(self): def is_class(self):
return inspect.isclass(self.obj) return self.access.is_class()
def py__doc__(self, include_call_signature=False): def py__doc__(self, include_call_signature=False):
return inspect.getdoc(self.obj) or '' return self.access.py__doc__()
def get_param_names(self): def get_param_names(self):
obj = self.obj obj = self.obj
@@ -153,7 +134,7 @@ class CompiledObject(Context):
yield SignatureParamName(self, signature_param) yield SignatureParamName(self, signature_param)
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, repr(self.obj)) return '<%s: %s>' % (self.__class__.__name__, self.access.get_repr())
@underscore_memoization @underscore_memoization
def _parse_function_doc(self): def _parse_function_doc(self):
@@ -165,16 +146,7 @@ class CompiledObject(Context):
@property @property
def api_type(self): def api_type(self):
obj = self.obj return self.access.get_api_type()
if inspect.isclass(obj):
return 'class'
elif inspect.ismodule(obj):
return 'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return 'function'
# Everything else...
return 'instance'
@underscore_memoization @underscore_memoization
def _cls(self): def _cls(self):
@@ -185,18 +157,6 @@ class CompiledObject(Context):
# Ensures that a CompiledObject is returned that is not an instance (like list) # Ensures that a CompiledObject is returned that is not an instance (like list)
return self return self
def _get_class(self):
if not fake.is_class_instance(self.obj) or \
inspect.ismethoddescriptor(self.obj): # slots
return self.obj
try:
return self.obj.__class__
except AttributeError:
# happens with numpy.core.umath._UFUNC_API (you get it
# automatically by doing `import numpy`.
return type
def get_filters(self, search_global=False, is_instance=False, def get_filters(self, search_global=False, is_instance=False,
until_position=None, origin_scope=None): until_position=None, origin_scope=None):
yield self._ensure_one_filter(is_instance) yield self._ensure_one_filter(is_instance)
@@ -211,36 +171,25 @@ class CompiledObject(Context):
@CheckAttribute @CheckAttribute
def py__getitem__(self, index): def py__getitem__(self, index):
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): access = self.access.py__getitem__(index)
# Get rid of side effects, we won't call custom `__getitem__`s. if access is None:
return ContextSet() return ContextSet()
return ContextSet(create(self.evaluator, self.obj[index])) return ContextSet(_create_from_access(self.evaluator, access))
@CheckAttribute @CheckAttribute
def py__iter__(self): def py__iter__list(self):
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): for access in self.access.py__iter__():
# Get rid of side effects, we won't call custom `__getitem__`s. yield _create_from_access(self.evaluator, access)
return
for i, part in enumerate(self.obj):
if i > 20:
# Should not go crazy with large iterators
break
yield LazyKnownContext(create(self.evaluator, part))
def py__name__(self): def py__name__(self):
try: return self.access.py__name__()
return self._get_class().__name__
except AttributeError:
return None
@property @property
def name(self): def name(self):
try: name = self.py__name__()
name = self._get_class().__name__ if name is None:
except AttributeError: name = self.access.get_repr()
name = repr(self.obj)
return CompiledContextName(self, name) return CompiledContextName(self, name)
def _execute_function(self, params): def _execute_function(self, params):
@@ -274,6 +223,15 @@ class CompiledObject(Context):
create(self.evaluator, v) for v in self.obj.values() create(self.evaluator, v) for v in self.obj.values()
) )
def get_safe_value(self, default=_sentinel):
return self.access.get_safe_value(default=default)
def execute_operation(self, other, operator):
return _create_from_access(
self.evaluator,
self.access.execute_operation(other.access, operator)
)
class CompiledName(AbstractNameDefinition): class CompiledName(AbstractNameDefinition):
def __init__(self, evaluator, parent_context, name): def __init__(self, evaluator, parent_context, name):
@@ -294,9 +252,8 @@ class CompiledName(AbstractNameDefinition):
@underscore_memoization @underscore_memoization
def infer(self): def infer(self):
module = self.parent_context.get_root_context()
return ContextSet(_create_from_name( return ContextSet(_create_from_name(
self._evaluator, module, self.parent_context, self.string_name self._evaluator, self.parent_context, self.string_name
)) ))
@@ -366,30 +323,20 @@ class CompiledObjectFilter(AbstractFilter):
@memoize_method @memoize_method
def get(self, name): def get(self, name):
name = str(name) name = str(name)
obj = self._compiled_object.obj if not self._compiled_object.access.is_allowed_getattr(name):
try:
attr, is_get_descriptor = getattr_static(obj, name)
except AttributeError:
return []
else:
if is_get_descriptor \
and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
return [EmptyCompiledName(self._evaluator, name)] return [EmptyCompiledName(self._evaluator, name)]
if self._is_instance and name not in dir(obj):
if self._is_instance and name not in self._compiled_object.access.dir():
return [] return []
return [self._create_name(name)] return [self._create_name(name)]
def values(self): def values(self):
obj = self._compiled_object.obj
names = [] names = []
for name in dir(obj): for name in self._compiled_object.access.dir():
names += self.get(name) names += self.get(name)
# ``dir`` doesn't include the type names. # ``dir`` doesn't include the type names.
if not self._is_instance and inspect.isclass(obj) and obj != type: if not self._is_instance and self._compiled_object.access.needs_type_completions():
for filter in create(self._evaluator, type).get_filters(): for filter in create(self._evaluator, type).get_filters():
names += filter.values() names += filter.values()
return names return names
@@ -533,24 +480,18 @@ def _parse_function_doc(doc):
return param_str, ret return param_str, ret
def _create_from_name(evaluator, module, compiled_object, name): def _create_from_name(evaluator, compiled_object, name):
obj = compiled_object.obj
faked = None faked = None
print(compiled_object.tree_node)
try: try:
faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name) faked = fake.get_faked_with_parent_context(compiled_object, name)
if faked.type == 'funcdef': if faked.type == 'funcdef':
from jedi.evaluate.context.function import FunctionContext from jedi.evaluate.context.function import FunctionContext
return FunctionContext(evaluator, compiled_object, faked) return FunctionContext(evaluator, compiled_object, faked)
except fake.FakeDoesNotExist: except fake.FakeDoesNotExist:
pass pass
try: obj = compiled_object.access.getattr(name, default=None)
obj = getattr(obj, name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
obj = None
return create(evaluator, obj, parent_context=compiled_object, faked=faked) return create(evaluator, obj, parent_context=compiled_object, faked=faked)
@@ -609,20 +550,45 @@ def create(evaluator, obj, parent_context=None, module=None, faked=None):
A very weird interface class to this module. The more options provided the A very weird interface class to this module. The more options provided the
more acurate loading compiled objects is. more acurate loading compiled objects is.
""" """
print('create', obj)
if isinstance(obj, DirectObjectAccess):
access = obj
else:
access = DirectObjectAccess(obj)
if inspect.ismodule(obj): if inspect.ismodule(obj):
if parent_context is not None: if parent_context is not None:
# Modules don't have parents, be careful with caching: recurse. # Modules don't have parents, be careful with caching: recurse.
return create(evaluator, obj) return create(evaluator, obj)
else:
if parent_context is None and obj is not _builtins:
return create(evaluator, obj, create(evaluator, _builtins))
#if parent_context is None and obj is not _builtins:
#return create(evaluator, obj, create(evaluator, _builtins))
if faked is None and parent_context is None:
access_tuples = access.get_access_path_tuples()
if access_tuples:
string_names, accesses = zip(*access_tuples)
try: try:
faked = fake.get_faked(evaluator, module, obj, parent_context=parent_context) tree_nodes = fake.get_faked_tree_nodes(evaluator.latest_grammar, string_names)
if faked.type == 'funcdef':
from jedi.evaluate.context.function import FunctionContext
return FunctionContext(evaluator, parent_context, faked)
except fake.FakeDoesNotExist: except fake.FakeDoesNotExist:
pass pass
else:
for access2, tree_node in zip(accesses, tree_nodes):
parent_context = CompiledObject(evaluator, access2, parent_context, tree_node)
print('foo', obj, tree_nodes, parent_context)
return CompiledObject(evaluator, obj, parent_context, faked) # TODO this if is ugly. Please remove, it may make certain
# properties of that function unusable.
if tree_node.type == 'function':
from jedi.evaluate.context.function import FunctionContext
return FunctionContext(evaluator, parent_context.parent_context, tree_node)
return parent_context
if parent_context is None:
parent_context = create(evaluator, _builtins)
return CompiledObject(evaluator, access, parent_context, faked)
def _create_from_access(evaluator, access, parent_context=None, faked=None):
if parent_context is None:
parent_context = create(evaluator, _builtins)
return CompiledObject(evaluator, access, parent_context, faked)

View File

@@ -0,0 +1,253 @@
import inspect
import types
import operator as op
from jedi._compatibility import unicode, is_py3, is_py34, builtins
from jedi.evaluate.compiled.getattr_static import getattr_static
MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have
# a __class__ attribute.
NOT_CLASS_TYPES = (
types.BuiltinFunctionType,
types.CodeType,
types.FrameType,
types.FunctionType,
types.GeneratorType,
types.GetSetDescriptorType,
types.LambdaType,
types.MemberDescriptorType,
types.MethodType,
types.ModuleType,
types.TracebackType,
MethodDescriptorType
)
if is_py3:
NOT_CLASS_TYPES += (
types.MappingProxyType,
types.SimpleNamespace
)
if is_py34:
NOT_CLASS_TYPES += (types.DynamicClassAttribute,)
# Those types don't exist in typing.
MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__)
# `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object)
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
ALLOWED_DESCRIPTOR_ACCESS = (
types.FunctionType,
types.GetSetDescriptorType,
types.MemberDescriptorType,
MethodDescriptorType,
WrapperDescriptorType,
ClassMethodDescriptorType,
staticmethod,
classmethod,
)
_sentinel = object()
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
_OPERATORS = {
'+': op.add,
'-': op.sub,
}
_OPERATORS.update(COMPARISON_OPERATORS)
class DirectObjectAccess(object):
def __init__(self, obj):
self._obj = obj
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._obj)
def _create_access(self, obj):
return DirectObjectAccess(obj)
def py__bool__(self):
return bool(self._obj)
def py__file__(self):
try:
return self._obj.__file__
except AttributeError:
return None
def py__doc__(self, include_call_signature=False):
return inspect.getdoc(self._obj) or ''
def py__name__(self):
if not is_class_instance(self._obj) or \
inspect.ismethoddescriptor(self._obj): # slots
cls = self._obj
else:
try:
cls = self._obj.__class__
except AttributeError:
# happens with numpy.core.umath._UFUNC_API (you get it
# automatically by doing `import numpy`.
return None
try:
return cls.__name__
except AttributeError:
return None
def py__mro__accesses(self):
return tuple(self._create_access(cls) for cls in self._obj.__mro__[1:])
def py__getitem__(self, index):
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return None
return self._create_access(self._obj[index])
def py__iter__list(self):
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return []
lst = []
for i, part in enumerate(self._obj):
if i > 20:
# Should not go crazy with large iterators
break
lst.append(self._create_access(part))
return lst
def get_repr(self):
return repr(self._obj)
def is_class(self):
return inspect.isclass(self._obj)
def dir(self):
return dir(self._obj)
def has_iter(self):
try:
iter(self._obj)
return True
except TypeError:
return False
def is_allowed_getattr(self, name):
try:
attr, is_get_descriptor = getattr_static(self._obj, name)
except AttributeError:
return []
else:
if is_get_descriptor \
and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
return False
return True
def getattr(self, name, default=_sentinel):
try:
return self._create_access(getattr(self._obj, name))
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
if default is _sentinel:
raise
return None
def get_safe_value(self, default=_sentinel):
if type(self._obj) in (float, int, str, unicode, slice, type(Ellipsis)):
return self._obj
if default == _sentinel:
raise ValueError
def get_api_type(self):
obj = self._obj
if self.is_class():
return 'class'
elif inspect.ismodule(obj):
return 'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return 'function'
# Everything else...
return 'instance'
def get_access_path_tuples(self):
path = self._get_objects_path()
try:
# Just provoke an AttributeError.
result = [(o.__name__, o) for o in path]
except AttributeError:
return []
else:
return [(name, self._create_access(o)) for name, o in result]
def _get_objects_path(self):
def get():
obj = self._obj
yield obj
try:
obj = obj.__objclass__
except AttributeError:
pass
else:
yield obj
try:
# Returns a dotted string path.
imp_plz = obj.__module__
except AttributeError:
# Unfortunately in some cases like `int` there's no __module__
if not inspect.ismodule(obj):
yield builtins
else:
if imp_plz is None:
# Happens for example in `(_ for _ in []).send.__module__`.
yield builtins
else:
try:
yield __import__(imp_plz)
except ImportError:
# __module__ can be something arbitrary that doesn't exist.
yield builtins
return list(reversed(list(get())))
def execute_operation(self, other, operator):
op = _OPERATORS[operator]
return self._create_access(op(self._obj, other._obj))
def needs_type_completions(self):
return inspect.isclass(self._obj) and self._obj != type
def is_class_instance(obj):
"""Like inspect.* methods."""
try:
cls = obj.__class__
except AttributeError:
return False
else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)

View File

@@ -6,63 +6,51 @@ mixing in Python code, the autocompletion should work much better for builtins.
import os import os
import inspect import inspect
import types
from itertools import chain from itertools import chain
from parso.python import tree from parso.python import tree
from jedi._compatibility import is_py3, builtins, unicode, is_py34 from jedi._compatibility import is_py3, builtins, unicode
modules = {} fake_modules = {}
MethodDescriptorType = type(str.replace) def _get_path_dict():
# These are not considered classes and access is granted even though they have path = os.path.dirname(os.path.abspath(__file__))
# a __class__ attribute. base_path = os.path.join(path, 'fake')
NOT_CLASS_TYPES = ( dct = {}
types.BuiltinFunctionType, for file_name in os.listdir(base_path):
types.CodeType, if file_name.endswith('.pym'):
types.FrameType, dct[file_name[:-4]] = os.path.join(base_path, file_name)
types.FunctionType, return dct
types.GeneratorType,
types.GetSetDescriptorType,
types.LambdaType,
types.MemberDescriptorType,
types.MethodType,
types.ModuleType,
types.TracebackType,
MethodDescriptorType
)
if is_py3:
NOT_CLASS_TYPES += ( _path_dict = _get_path_dict()
types.MappingProxyType,
types.SimpleNamespace
)
if is_py34:
NOT_CLASS_TYPES += (types.DynamicClassAttribute,)
class FakeDoesNotExist(Exception): class FakeDoesNotExist(Exception):
pass pass
def _load_faked_module(grammar, module): def _load_faked_module(grammar, module_name):
module_name = module.__name__
if module_name == '__builtin__' and not is_py3: if module_name == '__builtin__' and not is_py3:
module_name = 'builtins' module_name = 'builtins'
try: try:
return modules[module_name] return fake_modules[module_name]
except KeyError: except KeyError:
path = os.path.dirname(os.path.abspath(__file__)) pass
try: try:
with open(os.path.join(path, 'fake', module_name) + '.pym') as f: path = _path_dict[module_name]
source = f.read() except KeyError:
except IOError: fake_modules[module_name] = None
modules[module_name] = None
return return
modules[module_name] = m = grammar.parse(unicode(source))
with open(path) as f:
source = f.read()
fake_modules[module_name] = m = grammar.parse(unicode(source))
if module_name == 'builtins' and not is_py3: if module_name == 'builtins' and not is_py3:
# There are two implementations of `open` for either python 2/3. # There are two implementations of `open` for either python 2/3.
@@ -80,31 +68,6 @@ def _search_scope(scope, obj_name):
return s return s
def _get_module(obj):
if inspect.ismodule(obj):
return obj
try:
obj = obj.__objclass__
except AttributeError:
pass
try:
imp_plz = obj.__module__
except AttributeError:
# Unfortunately in some cases like `int` there's no __module__
return builtins
else:
if imp_plz is None:
# Happens for example in `(_ for _ in []).send.__module__`.
return builtins
else:
try:
return __import__(imp_plz)
except ImportError:
# __module__ can be something arbitrary that doesn't exist.
return builtins
def _faked(grammar, module, obj, name): def _faked(grammar, module, obj, name):
# Crazy underscore actions to try to escape all the internal madness. # Crazy underscore actions to try to escape all the internal madness.
if module is None: if module is None:
@@ -188,26 +151,24 @@ def _get_faked(grammar, module, obj, name=None):
return result, fake_module return result, fake_module
def get_faked(evaluator, module, obj, name=None, parent_context=None): def get_faked_with_parent_context(parent_context, name):
if parent_context and parent_context.tree_node is not None: if parent_context.tree_node is not None:
# Try to search in already clearly defined stuff. # Try to search in already clearly defined stuff.
found = _search_scope(parent_context.tree_node, name) found = _search_scope(parent_context.tree_node, name)
if found is not None: if found is not None:
return found return found
else:
raise FakeDoesNotExist raise FakeDoesNotExist
faked, fake_module = _get_faked(evaluator.latest_grammar, module and module.obj, obj, name)
if module is not None:
module.get_used_names = fake_module.get_used_names
return faked
def get_faked_tree_nodes(grammar, string_names):
module = base = _load_faked_module(grammar, string_names[0])
if module is None:
raise FakeDoesNotExist
def is_class_instance(obj): tree_nodes = [module]
"""Like inspect.* methods.""" for name in string_names[1:]:
try: base = _search_scope(base, name)
cls = obj.__class__ if base is None:
except AttributeError: raise FakeDoesNotExist
return False tree_nodes.append(base)
else: return tree_nodes
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)

View File

@@ -306,7 +306,7 @@ class BoundMethod(FunctionContext):
class CompiledBoundMethod(compiled.CompiledObject): class CompiledBoundMethod(compiled.CompiledObject):
def __init__(self, func): def __init__(self, func):
super(CompiledBoundMethod, self).__init__( super(CompiledBoundMethod, self).__init__(
func.evaluator, func.obj, func.parent_context, func.tree_node) func.evaluator, func.access, func.parent_context, func.tree_node)
def get_param_names(self): def get_param_names(self):
return list(super(CompiledBoundMethod, self).get_param_names())[1:] return list(super(CompiledBoundMethod, self).get_param_names())[1:]

View File

@@ -300,7 +300,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable):
for key, value in self._items(): for key, value in self._items():
for k in self._defining_context.eval_node(key): for k in self._defining_context.eval_node(key):
if isinstance(k, compiled.CompiledObject) \ if isinstance(k, compiled.CompiledObject) \
and index == k.obj: and index == k.get_safe_value(default=None):
return self._defining_context.eval_node(value) return self._defining_context.eval_node(value)
raise KeyError('No key found in dictionary %s.' % self) raise KeyError('No key found in dictionary %s.' % self)

View File

@@ -183,7 +183,7 @@ def is_compiled(context):
def is_string(context): def is_string(context):
return is_compiled(context) and isinstance(context.obj, (str, unicode)) return is_compiled(context) and isinstance(context.get_safe_value(default=None), (str, unicode))
def is_literal(context): def is_literal(context):
@@ -191,4 +191,4 @@ def is_literal(context):
def is_number(context): def is_number(context):
return is_compiled(context) and isinstance(context.obj, (int, float)) return is_compiled(context) and isinstance(context.get_safe_value(default=None), (int, float))

View File

@@ -22,6 +22,7 @@ from jedi.evaluate.context import iterable
from jedi.evaluate.context import TreeInstance, CompiledInstance from jedi.evaluate.context import TreeInstance, CompiledInstance
from jedi.evaluate.finder import NameFinder from jedi.evaluate.finder import NameFinder
from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled
from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
def _limit_context_infers(func): def _limit_context_infers(func):
@@ -318,19 +319,6 @@ def eval_factor(context_set, operator):
yield context yield context
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
def _literals_to_types(evaluator, result): def _literals_to_types(evaluator, result):
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(), # Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
# int(), float(), etc). # int(), float(), etc).
@@ -384,30 +372,29 @@ def _eval_comparison_part(evaluator, context, left, operator, right):
return ContextSet(right) return ContextSet(right)
elif operator == '+': elif operator == '+':
if l_is_num and r_is_num or is_string(left) and is_string(right): if l_is_num and r_is_num or is_string(left) and is_string(right):
return ContextSet(compiled.create(evaluator, left.obj + right.obj)) return ContextSet(left.execute_operation(right, operator))
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right): elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
return ContextSet(iterable.MergedArray(evaluator, (left, right))) return ContextSet(iterable.MergedArray(evaluator, (left, right)))
elif operator == '-': elif operator == '-':
if l_is_num and r_is_num: if l_is_num and r_is_num:
return ContextSet(compiled.create(evaluator, left.obj - right.obj)) return ContextSet(left.execute_operation(right, operator))
elif operator == '%': elif operator == '%':
# With strings and numbers the left type typically remains. Except for # With strings and numbers the left type typically remains. Except for
# `int() % float()`. # `int() % float()`.
return ContextSet(left) return ContextSet(left)
elif operator in COMPARISON_OPERATORS: elif operator in COMPARISON_OPERATORS:
operation = COMPARISON_OPERATORS[operator]
if is_compiled(left) and is_compiled(right): if is_compiled(left) and is_compiled(right):
# Possible, because the return is not an option. Just compare. # Possible, because the return is not an option. Just compare.
left = left.obj
right = right.obj
try: try:
result = operation(left, right) return ContextSet(left.execute_operation(right, operator))
except TypeError: except TypeError:
# Could be True or False. # Could be True or False.
return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False)) pass
else: else:
raise NotImplementedError
return ContextSet(compiled.create(evaluator, result)) return ContextSet(compiled.create(evaluator, result))
return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False))
elif operator == 'in': elif operator == 'in':
return NO_CONTEXTS return NO_CONTEXTS

View File

@@ -11,7 +11,8 @@ def test_sys_path():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'version', 'version',
['2.6', '2.7', '3.3', '3.4', '3.5', '3.6', '3.7'] # TODO add '2.6', '2.7',
['3.3', '3.4', '3.5', '3.6', '3.7']
) )
def test_versions(version): def test_versions(version):
executable = 'python' + version executable = 'python' + version