""" Imitate the parser representation. """ import inspect import re import sys import os import types from functools import partial from jedi._compatibility import builtins as _builtins 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 DirectObjectAccess, _sentinel, create_access from jedi.evaluate.cache import evaluator_function_cache from . import fake _sep = os.path.sep if os.path.altsep is not None: _sep += os.path.altsep _path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) del _sep 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): path = None # modules have this attribute - set it to None. used_names = lambda self: {} # To be consistent with modules. 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 def get_root_node(self): # To make things a bit easier with filters we add this method here. return self.get_root_context() @CheckAttribute def py__call__(self, 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(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): raise NotImplementedError return tuple(create(self.evaluator, cls) for cls in self.obj.__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 get_self_attributes(self): return [] # Instance compatibility def get_imports(self): return [] # Builtins don't have imports def dict_values(self): return ContextSet.from_iterable( create(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(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.default is not p.empty: contexts = ContextSet(create(evaluator, p.default)) if p.annotation is not p.empty: annotation = create(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. 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): """ Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e. compares the path with sys.path and then returns the dotted_path. If the path is not in the sys.path, just returns None. """ if os.path.basename(fs_path).startswith('__init__.'): # We are calculating the path. __init__ files are not interesting. fs_path = os.path.dirname(fs_path) # prefer # - UNIX # /path/to/pythonX.Y/lib-dynload # /path/to/pythonX.Y/site-packages # - Windows # C:\path\to\DLLs # C:\path\to\Lib\site-packages # over # - UNIX # /path/to/pythonX.Y # - Windows # C:\path\to\Lib path = '' for s in sys_path: if (fs_path.startswith(s) and len(path) < len(s)): path = s # - Window # X:\path\to\lib-dynload/datetime.pyd => datetime module_path = fs_path[len(path):].lstrip(os.path.sep).lstrip('/') # - Window # Replace like X:\path\to\something/foo/bar.py 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) if faked.type == 'funcdef': from jedi.evaluate.context.function import FunctionContext return FunctionContext(evaluator, compiled_object, faked) except fake.FakeDoesNotExist: pass access = compiled_object.access.getattr(name, default=None) return create(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] if identifier == 'BUILTINS': parent_context = None else: parent_context = create(evaluator, _builtins) return create(evaluator, obj, parent_context=parent_context) 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, parent_context=None, faked=None): if inspect.ismodule(obj): if parent_context is not None: # Modules don't have parents, be careful with caching: recurse. return create(evaluator, obj) if isinstance(obj, DirectObjectAccess): return _create(evaluator, obj, parent_context, faked) else: return _create(evaluator, create_access(evaluator, obj), parent_context, faked) @_normalize_create_args @evaluator_function_cache() def _create(evaluator, access, parent_context=None, faked=None): """ A very weird interface class to this module. The more options provided the more acurate loading compiled objects is. """ #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: 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 = create(evaluator, access2, parent_context, faked=tree_node) # 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 # TODO wow this is a mess.... if parent_context is None and not faked: parent_context = create(evaluator, _builtins) return create(evaluator, access, parent_context) 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 create(evaluator, access, parent_context, faked=faked)