From 78cc015b9d03538f790b241826e29a910f58b76d Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 10 Jan 2014 22:58:49 +0100 Subject: [PATCH] start introducing the compiled.fake module that fakes builtin code --- jedi/evaluate/compiled/__init__.py | 27 ++++++-- jedi/evaluate/compiled/fake.py | 106 +++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 jedi/evaluate/compiled/fake.py diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 82083fee..19f6e404 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -11,6 +11,7 @@ from jedi import debug from jedi.parser.representation import Base from jedi.cache import underscore_memoization from jedi.evaluate.sys_path import get_sys_path +from . import fake # TODO @@ -51,9 +52,7 @@ class PyObject(Base): @underscore_memoization def _cls(self): # Ensures that a PyObject is returned that is not an instance (like list) - if not (inspect.isclass(self.obj) or inspect.ismodule(self.obj) - or inspect.isbuiltin(self.obj) or inspect.ismethod(self.obj) - or inspect.ismethoddescriptor(self.obj)): + if fake.is_class_instance(self.obj): return PyObject(self.obj.__class__, self.parent, True) return self @@ -218,5 +217,23 @@ builtin = PyObject(_builtins) magic_function_class = PyObject(type(load_module), parent=builtin) -def create(obj): - return PyObject(obj, builtin) +def create(obj, parent=builtin, instantiated=False, module=None): + if module is None: + if not inspect.ismodule(obj): + module = obj.__class__ if fake.is_class_instance(obj) else obj + if not (inspect.isbuiltin(module) or inspect.isclass(module)): + module = obj.__objclass__ + try: + imp_plz = obj.__module__ + except AttributeError: + # Unfortunately in some cases like `int` there's no __module__ + module = builtin + else: + module = PyObject(__import__(imp_plz)) + + faked = fake.get_faked(module.obj, obj) + if faked is not None: + faked.parent = parent + return faked + + return PyObject(obj, parent, instantiated) diff --git a/jedi/evaluate/compiled/fake.py b/jedi/evaluate/compiled/fake.py new file mode 100644 index 00000000..f2a8e442 --- /dev/null +++ b/jedi/evaluate/compiled/fake.py @@ -0,0 +1,106 @@ +""" +Loads functions that are mixed in to the standard library. E.g. builtins are +written in C (binaries), but my autocompletion only understands Python code. By +mixing in Python code, the autocompletion should work much better for builtins. +""" + +import re +import os +import inspect + +from jedi._compatibility import is_py3k +from jedi.parser import Parser + +modules = {} + + +def _load_fakes(module_name): + regex = r'^(def|class)\s+([\w\d]+)' + + def process_code(code, depth=0): + funcs = {} + matches = list(re.finditer(regex, code, re.MULTILINE)) + positions = [m.start() for m in matches] + for i, pos in enumerate(positions): + try: + code_block = code[pos:positions[i + 1]] + except IndexError: + code_block = code[pos:len(code)] + structure_name = matches[i].group(1) + name = matches[i].group(2) + if structure_name == 'def': + funcs[name] = code_block + elif structure_name == 'class': + if depth > 0: + raise NotImplementedError() + + # remove class line + c = re.sub(r'^[^\n]+', '', code_block) + # remove whitespace + c = re.compile(r'^[ ]{4}', re.MULTILINE).sub('', c) + + funcs[name] = process_code(c) + else: + raise NotImplementedError() + return funcs + + # sometimes there are stupid endings like `_sqlite3.cpython-32mu` + module_name = re.sub(r'\..*', '', module_name) + + if module_name == '__builtin__' and not is_py3k: + module_name = 'builtins' + path = os.path.dirname(os.path.abspath(__file__)) + try: + with open(os.path.join(path, 'mixin', module_name) + '.pym') as f: + s = f.read() + except IOError: + return {} + else: + mixin_dct = process_code(s) + if is_py3k and module_name == 'builtins': + # in the case of Py3k xrange is now range + mixin_dct['range'] = mixin_dct['xrange'] + return mixin_dct + + +def _load_module(module): + module_name = module.__name__ + try: + return 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: + return {} + module = Parser(source).module + modules[module_name] = module + return module + + +def get_faked(module, obj): + def from_scope(scope, obj): + for s in scope.subscopes: + if s.name == obj.name: + return s + + mod = _load_module(module) + + # Having the module as a `parser.representation.module`, we need to scan + # for methods. + if is_class_instance(obj): + obj = obj.__class__ + if inspect.isbuiltin(obj): + return from_scope(mod, obj) + elif not inspect.isclass(obj): + # object is a method or descriptor + cls = obj.__objclass__ + return from_scope(from_scope(mod, cls), obj) + + +def is_class_instance(obj): + """Like inspect.* methods.""" + return not (inspect.isclass(obj) or inspect.ismodule(obj) + or inspect.isbuiltin(obj) or inspect.ismethod(obj) + or inspect.ismethoddescriptor(obj))