From 7263d8565bc677ec8c123345afcd1cb483096959 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 25 Nov 2017 19:47:49 +0100 Subject: [PATCH] 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 --- jedi/api/__init__.py | 2 +- jedi/evaluate/base_context.py | 7 +- jedi/evaluate/compiled/__init__.py | 196 +++++++++------------- jedi/evaluate/compiled/access.py | 253 +++++++++++++++++++++++++++++ jedi/evaluate/compiled/fake.py | 139 ++++++---------- jedi/evaluate/context/instance.py | 2 +- jedi/evaluate/context/iterable.py | 2 +- jedi/evaluate/helpers.py | 4 +- jedi/evaluate/syntax_tree.py | 35 ++-- test/test_api/test_environment.py | 3 +- 10 files changed, 408 insertions(+), 235 deletions(-) create mode 100644 jedi/evaluate/compiled/access.py diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index bfdaf528..cef8c252 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -16,7 +16,7 @@ import parso from parso.python import tree 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 settings from jedi import cache diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index 693a99aa..24df628a 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -85,8 +85,13 @@ class Context(BaseContext): result = ContextSet() for index in index_contexts: - if isinstance(index, (CompiledObject, Slice)): + if isinstance(index, Slice): 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 the index is not clearly defined, we have to get all the diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 83d07fdb..e0401b57 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -16,6 +16,7 @@ from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ from jedi.evaluate.base_context import Context, ContextSet from jedi.evaluate.lazy_context import LazyKnownContext from jedi.evaluate.compiled.getattr_static import getattr_static +from jedi.evaluate.compiled.access import DirectObjectAccess, _sentinel 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))) 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): """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 # just start with __getitem__(0). This is especially true for # Python 2 strings, where `str.__iter__` is not even defined. - try: - iter(instance.obj) - except TypeError: + if not instance.access.has_iter(): raise AttributeError else: - getattr(instance.obj, self.check_name) + instance.access.getattr(self.check_name) return partial(self.func, instance) @@ -73,9 +55,9 @@ class CompiledObject(Context): path = None # modules have this attribute - set it to None. 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) - self.obj = obj + self.access = access # This attribute will not be set for most classes, except for fakes. self.tree_node = faked_class @@ -85,7 +67,7 @@ class CompiledObject(Context): @CheckAttribute def py__call__(self, params): - if inspect.isclass(self.obj): + if self.access.is_class(): from jedi.evaluate.context import CompiledInstance return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params)) else: @@ -97,26 +79,25 @@ class CompiledObject(Context): @CheckAttribute 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 def py__bases__(self): return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__) def py__bool__(self): - return bool(self.obj) + return self.access.py__bool__() def py__file__(self): - try: - return self.obj.__file__ - except AttributeError: - return None + return self.access.py__file__() def is_class(self): - return inspect.isclass(self.obj) + return self.access.is_class() def py__doc__(self, include_call_signature=False): - return inspect.getdoc(self.obj) or '' + return self.access.py__doc__() def get_param_names(self): obj = self.obj @@ -153,7 +134,7 @@ class CompiledObject(Context): yield SignatureParamName(self, signature_param) def __repr__(self): - return '<%s: %s>' % (self.__class__.__name__, repr(self.obj)) + return '<%s: %s>' % (self.__class__.__name__, self.access.get_repr()) @underscore_memoization def _parse_function_doc(self): @@ -165,16 +146,7 @@ class CompiledObject(Context): @property def api_type(self): - obj = self.obj - 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' + return self.access.get_api_type() @underscore_memoization def _cls(self): @@ -185,18 +157,6 @@ class CompiledObject(Context): # Ensures that a CompiledObject is returned that is not an instance (like list) 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, until_position=None, origin_scope=None): yield self._ensure_one_filter(is_instance) @@ -211,36 +171,25 @@ class CompiledObject(Context): @CheckAttribute 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. + access = self.access.py__getitem__(index) + if access is None: return ContextSet() - return ContextSet(create(self.evaluator, self.obj[index])) + return ContextSet(_create_from_access(self.evaluator, access)) @CheckAttribute - def py__iter__(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 - - 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__iter__list(self): + for access in self.access.py__iter__(): + yield _create_from_access(self.evaluator, access) def py__name__(self): - try: - return self._get_class().__name__ - except AttributeError: - return None + return self.access.py__name__() @property def name(self): - try: - name = self._get_class().__name__ - except AttributeError: - name = repr(self.obj) + name = self.py__name__() + if name is None: + name = self.access.get_repr() return CompiledContextName(self, name) def _execute_function(self, params): @@ -274,6 +223,15 @@ class CompiledObject(Context): 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): def __init__(self, evaluator, parent_context, name): @@ -294,9 +252,8 @@ class CompiledName(AbstractNameDefinition): @underscore_memoization def infer(self): - module = self.parent_context.get_root_context() 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 def get(self, name): name = str(name) - obj = self._compiled_object.obj - try: - attr, is_get_descriptor = getattr_static(obj, name) - except AttributeError: + if not self._compiled_object.access.is_allowed_getattr(name): + return [EmptyCompiledName(self._evaluator, name)] + + if self._is_instance and name not in self._compiled_object.access.dir(): 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)] - if self._is_instance and name not in dir(obj): - return [] return [self._create_name(name)] def values(self): - obj = self._compiled_object.obj - names = [] - for name in dir(obj): + 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 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(): names += filter.values() return names @@ -533,24 +480,18 @@ def _parse_function_doc(doc): return param_str, ret -def _create_from_name(evaluator, module, compiled_object, name): - obj = compiled_object.obj +def _create_from_name(evaluator, compiled_object, name): faked = None + print(compiled_object.tree_node) 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': from jedi.evaluate.context.function import FunctionContext return FunctionContext(evaluator, compiled_object, faked) except fake.FakeDoesNotExist: pass - try: - obj = getattr(obj, name) - except AttributeError: - # Happens e.g. in properties of - # PyQt4.QtGui.QStyleOptionComboBox.currentText - # -> just set it to None - obj = None + obj = compiled_object.access.getattr(name, default=None) 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 more acurate loading compiled objects is. """ + print('create', obj) + if isinstance(obj, DirectObjectAccess): + access = obj + else: + access = DirectObjectAccess(obj) if inspect.ismodule(obj): if parent_context is not None: # Modules don't have parents, be careful with caching: recurse. return create(evaluator, obj) - else: - if parent_context is None and obj is not _builtins: - return create(evaluator, obj, create(evaluator, _builtins)) - try: - faked = fake.get_faked(evaluator, module, obj, parent_context=parent_context) - if faked.type == 'funcdef': - from jedi.evaluate.context.function import FunctionContext - return FunctionContext(evaluator, parent_context, faked) - except fake.FakeDoesNotExist: - pass + #if parent_context is None and obj is not _builtins: + #return create(evaluator, obj, create(evaluator, _builtins)) - return CompiledObject(evaluator, obj, parent_context, faked) + 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: + tree_nodes = fake.get_faked_tree_nodes(evaluator.latest_grammar, string_names) + except fake.FakeDoesNotExist: + 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) + + # 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) diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py new file mode 100644 index 00000000..bf884c9f --- /dev/null +++ b/jedi/evaluate/compiled/access.py @@ -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) diff --git a/jedi/evaluate/compiled/fake.py b/jedi/evaluate/compiled/fake.py index f56c919d..dfddef14 100644 --- a/jedi/evaluate/compiled/fake.py +++ b/jedi/evaluate/compiled/fake.py @@ -6,72 +6,60 @@ mixing in Python code, the autocompletion should work much better for builtins. import os import inspect -import types from itertools import chain 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) -# 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 -) +def _get_path_dict(): + path = os.path.dirname(os.path.abspath(__file__)) + base_path = os.path.join(path, 'fake') + dct = {} + for file_name in os.listdir(base_path): + if file_name.endswith('.pym'): + dct[file_name[:-4]] = os.path.join(base_path, file_name) + return dct -if is_py3: - NOT_CLASS_TYPES += ( - types.MappingProxyType, - types.SimpleNamespace - ) - if is_py34: - NOT_CLASS_TYPES += (types.DynamicClassAttribute,) + +_path_dict = _get_path_dict() class FakeDoesNotExist(Exception): pass -def _load_faked_module(grammar, module): - module_name = module.__name__ +def _load_faked_module(grammar, module_name): if module_name == '__builtin__' and not is_py3: module_name = 'builtins' try: - return modules[module_name] + return fake_modules[module_name] except KeyError: - path = os.path.dirname(os.path.abspath(__file__)) - try: - with open(os.path.join(path, 'fake', module_name) + '.pym') as f: - source = f.read() - except IOError: - modules[module_name] = None - return - modules[module_name] = m = grammar.parse(unicode(source)) + pass - if module_name == 'builtins' and not is_py3: - # There are two implementations of `open` for either python 2/3. - # -> Rename the python2 version (`look at fake/builtins.pym`). - open_func = _search_scope(m, 'open') - open_func.children[1].value = 'open_python3' - open_func = _search_scope(m, 'open_python2') - open_func.children[1].value = 'open' - return m + try: + path = _path_dict[module_name] + except KeyError: + fake_modules[module_name] = None + return + + 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: + # There are two implementations of `open` for either python 2/3. + # -> Rename the python2 version (`look at fake/builtins.pym`). + open_func = _search_scope(m, 'open') + open_func.children[1].value = 'open_python3' + open_func = _search_scope(m, 'open_python2') + open_func.children[1].value = 'open' + return m def _search_scope(scope, obj_name): @@ -80,31 +68,6 @@ def _search_scope(scope, obj_name): 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): # Crazy underscore actions to try to escape all the internal madness. if module is None: @@ -188,26 +151,24 @@ def _get_faked(grammar, module, obj, name=None): return result, fake_module -def get_faked(evaluator, module, obj, name=None, parent_context=None): - if parent_context and parent_context.tree_node is not None: +def get_faked_with_parent_context(parent_context, name): + if parent_context.tree_node is not None: # Try to search in already clearly defined stuff. found = _search_scope(parent_context.tree_node, name) if found is not None: return found - else: + raise FakeDoesNotExist + + +def get_faked_tree_nodes(grammar, string_names): + module = base = _load_faked_module(grammar, string_names[0]) + if module is None: + raise FakeDoesNotExist + + tree_nodes = [module] + for name in string_names[1:]: + base = _search_scope(base, name) + if base is None: 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 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) + tree_nodes.append(base) + return tree_nodes diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index 2c8d796c..b6015350 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -306,7 +306,7 @@ class BoundMethod(FunctionContext): class CompiledBoundMethod(compiled.CompiledObject): def __init__(self, func): 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): return list(super(CompiledBoundMethod, self).get_param_names())[1:] diff --git a/jedi/evaluate/context/iterable.py b/jedi/evaluate/context/iterable.py index d0f468e4..9b6c644f 100644 --- a/jedi/evaluate/context/iterable.py +++ b/jedi/evaluate/context/iterable.py @@ -300,7 +300,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable): for key, value in self._items(): for k in self._defining_context.eval_node(key): if isinstance(k, compiled.CompiledObject) \ - and index == k.obj: + and index == k.get_safe_value(default=None): return self._defining_context.eval_node(value) raise KeyError('No key found in dictionary %s.' % self) diff --git a/jedi/evaluate/helpers.py b/jedi/evaluate/helpers.py index 6652287b..44a6f6b4 100644 --- a/jedi/evaluate/helpers.py +++ b/jedi/evaluate/helpers.py @@ -183,7 +183,7 @@ def is_compiled(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): @@ -191,4 +191,4 @@ def is_literal(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)) diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 1d847a49..ddfdf4c4 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -22,6 +22,7 @@ from jedi.evaluate.context import iterable from jedi.evaluate.context import TreeInstance, CompiledInstance from jedi.evaluate.finder import NameFinder 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): @@ -318,19 +319,6 @@ def eval_factor(context_set, operator): 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): # Changes literals ('a', 1, 1.0, etc) to its type instances (str(), # int(), float(), etc). @@ -384,30 +372,29 @@ def _eval_comparison_part(evaluator, context, left, operator, right): return ContextSet(right) elif operator == '+': 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): return ContextSet(iterable.MergedArray(evaluator, (left, right))) elif operator == '-': 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 == '%': # With strings and numbers the left type typically remains. Except for # `int() % float()`. return ContextSet(left) elif operator in COMPARISON_OPERATORS: - operation = COMPARISON_OPERATORS[operator] if is_compiled(left) and is_compiled(right): # Possible, because the return is not an option. Just compare. - left = left.obj - right = right.obj - - try: - result = operation(left, right) - except TypeError: - # Could be True or False. - return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False)) + try: + return ContextSet(left.execute_operation(right, operator)) + except TypeError: + # Could be True or False. + pass else: + raise NotImplementedError return ContextSet(compiled.create(evaluator, result)) + + return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False)) elif operator == 'in': return NO_CONTEXTS diff --git a/test/test_api/test_environment.py b/test/test_api/test_environment.py index efb0045c..736089ce 100644 --- a/test/test_api/test_environment.py +++ b/test/test_api/test_environment.py @@ -11,7 +11,8 @@ def test_sys_path(): @pytest.mark.parametrize( '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): executable = 'python' + version