Merge pull request #363 from davidhalter/builtin

Fixing Memory Issues in Compiled Modules
This commit is contained in:
Dave Halter
2014-01-13 03:20:00 -08:00
33 changed files with 737 additions and 670 deletions

View File

@@ -27,7 +27,7 @@ from jedi import keywords
from jedi.api import classes from jedi.api import classes
from jedi.evaluate import Evaluator, filter_private_variable from jedi.evaluate import Evaluator, filter_private_variable
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import builtin from jedi.evaluate import compiled
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import helpers from jedi.evaluate import helpers
@@ -137,20 +137,19 @@ class Script(object):
path, dot, like = self._get_completion_parts() path, dot, like = self._get_completion_parts()
user_stmt = self._user_stmt(True) user_stmt = self._user_stmt(True)
bs = builtin.Builtin.scope b = compiled.builtin
completions = get_completions(user_stmt, bs) completions = get_completions(user_stmt, b)
if not dot: if not dot:
# add named params # add named params
for call_def in self.call_signatures(): for call_def in self.call_signatures():
if not call_def.module.is_builtin(): if not isinstance(call_def.module, compiled.PyObject):
for p in call_def.params: for p in call_def.params:
completions.append((p.get_name(), p)) completions.append((p.get_name(), p))
if not path and not isinstance(user_stmt, pr.Import): if not path and not isinstance(user_stmt, pr.Import):
# add keywords # add keywords
completions += ((k, bs) for k in keywords.keyword_names( completions += ((k, b) for k in keywords.keyword_names(all=True))
all=True))
needs_dot = not dot and path needs_dot = not dot and path
@@ -193,7 +192,7 @@ class Script(object):
debug.dbg('possible scopes', scopes) debug.dbg('possible scopes', scopes)
for s in scopes: for s in scopes:
if s.isinstance(er.Function): if s.isinstance(er.Function):
names = s.get_magic_method_names() names = s.get_magic_function_names()
else: else:
if isinstance(s, imports.ImportPath): if isinstance(s, imports.ImportPath):
under = like + self._user_context.get_path_after_cursor() under = like + self._user_context.get_path_after_cursor()
@@ -519,7 +518,8 @@ class Script(object):
debug.speed('func_call followed') debug.speed('func_call followed')
return [classes.CallDef(o, index, call) for o in origins return [classes.CallDef(o, index, call) for o in origins
if o.isinstance(er.Function, er.Instance, er.Class)] if o.isinstance(er.Function, er.Instance, er.Class)
or isinstance(o, compiled.PyObject) and o.type() != 'module']
def _func_call_and_param_index(self): def _func_call_and_param_index(self):
debug.speed('func_call start') debug.speed('func_call start')
@@ -563,11 +563,10 @@ class Script(object):
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S) match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
return match.groups() return match.groups()
@staticmethod def _sorted_defs(self, d):
def _sorted_defs(d):
# Note: `or ''` below is required because `module_path` could be # Note: `or ''` below is required because `module_path` could be
# None and you can't compare None and str in Python 3. # None and you can't compare None and str in Python 3.
return sorted(d, key=lambda x: (x.module_path or '', x.line, x.column)) return sorted(d, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
class Interpreter(Script): class Interpreter(Script):

View File

@@ -14,6 +14,7 @@ from jedi import cache
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import compiled
from jedi import keywords from jedi import keywords
@@ -70,8 +71,11 @@ class BaseDefinition(object):
# generate a path to the definition # generate a path to the definition
self._module = definition.get_parent_until() self._module = definition.get_parent_until()
self.module_path = self._module.path if self.in_builtin_module():
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``""" self.module_path = None
else:
self.module_path = self._module.path
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``"""
@property @property
def start_pos(self): def start_pos(self):
@@ -134,8 +138,10 @@ class BaseDefinition(object):
""" """
# generate the type # generate the type
stripped = self._definition stripped = self._definition
if isinstance(self._definition, er.InstanceElement): if isinstance(stripped, compiled.PyObject):
stripped = self._definition.var return stripped.type()
if isinstance(stripped, er.InstanceElement):
stripped = stripped.var
if isinstance(stripped, pr.Name): if isinstance(stripped, pr.Name):
stripped = stripped.parent stripped = stripped.parent
return type(stripped).__name__.lower() return type(stripped).__name__.lower()
@@ -167,18 +173,17 @@ class BaseDefinition(object):
The module name. The module name.
>>> from jedi import Script >>> from jedi import Script
>>> source = 'import datetime' >>> source = 'import json'
>>> script = Script(source, 1, len(source), 'example.py') >>> script = Script(source, path='example.py')
>>> d = script.goto_definitions()[0] >>> d = script.goto_definitions()[0]
>>> print(d.module_name) # doctest: +ELLIPSIS >>> print(d.module_name) # doctest: +ELLIPSIS
datetime json
""" """
return str(self._module.name) return str(self._module.name)
def in_builtin_module(self): def in_builtin_module(self):
"""Whether this is a builtin module.""" """Whether this is a builtin module."""
return not (self.module_path is None or return isinstance(self._module, compiled.PyObject)
self.module_path.endswith('.py'))
@property @property
def line_nr(self): def line_nr(self):
@@ -232,7 +237,7 @@ class BaseDefinition(object):
""" """
try: try:
return self._definition.doc return self._definition.doc or '' # Always a String, never None.
except AttributeError: except AttributeError:
return self.raw_doc return self.raw_doc
@@ -435,7 +440,9 @@ class Definition(BaseDefinition):
if isinstance(d, er.InstanceElement): if isinstance(d, er.InstanceElement):
d = d.var d = d.var
if isinstance(d, pr.Name): if isinstance(d, compiled.PyObject):
return d.name
elif isinstance(d, pr.Name):
return d.names[-1] if d.names else None return d.names[-1] if d.names else None
elif isinstance(d, iterable.Array): elif isinstance(d, iterable.Array):
return unicode(d.type) return unicode(d.type)
@@ -454,7 +461,6 @@ class Definition(BaseDefinition):
return d.assignment_details[0][1].values[0][0].name.names[-1] return d.assignment_details[0][1].values[0][0].name.names[-1]
except IndexError: except IndexError:
return None return None
return None
@property @property
def description(self): def description(self):
@@ -490,7 +496,9 @@ class Definition(BaseDefinition):
if isinstance(d, pr.Name): if isinstance(d, pr.Name):
d = d.parent d = d.parent
if isinstance(d, iterable.Array): if isinstance(d, compiled.PyObject):
d = d.type() + ' ' + d.name
elif isinstance(d, iterable.Array):
d = 'class ' + d.type d = 'class ' + d.type
elif isinstance(d, (pr.Class, er.Class, er.Instance)): elif isinstance(d, (pr.Class, er.Class, er.Instance)):
d = 'class ' + unicode(d.name) d = 'class ' + unicode(d.name)
@@ -518,12 +526,7 @@ class Definition(BaseDefinition):
.. todo:: Add full path. This function is should return a .. todo:: Add full path. This function is should return a
`module.class.function` path. `module.class.function` path.
""" """
if self.module_path.endswith('.py') \ position = '' if self.in_builtin_module else '@%s' % (self.line)
and not isinstance(self._definition, pr.Module):
position = '@%s' % (self.line)
else:
# is a builtin or module
position = ''
return "%s:%s%s" % (self.module_name, self.description, position) return "%s:%s%s" % (self.module_name, self.description, position)
def defined_names(self): def defined_names(self):

View File

@@ -4,13 +4,13 @@ import os
import time import time
try: try:
if not os.name == 'nt': if os.name == 'nt':
# does not work on Windows, as pyreadline and colorama interfere
raise ImportError
else:
# Use colorama for nicer console output. # Use colorama for nicer console output.
from colorama import Fore, init from colorama import Fore, init
init() init()
# does not work on Windows, as pyreadline and colorama interfere
else:
raise ImportError
except ImportError: except ImportError:
class Fore(object): class Fore(object):
RED = '' RED = ''
@@ -29,11 +29,24 @@ enable_notice = False
# callback, interface: level, str # callback, interface: level, str
debug_function = None debug_function = None
ignored_modules = ['jedi.evaluate.builtin', 'jedi.parser'] ignored_modules = ['jedi.evaluate.builtin', 'jedi.parser']
debug_indent = -1
def reset_time(): def reset_time():
global start_time global start_time, debug_indent
start_time = time.time() start_time = time.time()
debug_indent = -1
def increase_indent(func):
"""Decorator for makin """
def wrapper(*args, **kwargs):
global debug_indent
debug_indent += 1
result = func(*args, **kwargs)
debug_indent -= 1
return result
return wrapper
def dbg(*args): def dbg(*args):
@@ -42,18 +55,21 @@ def dbg(*args):
frm = inspect.stack()[1] frm = inspect.stack()[1]
mod = inspect.getmodule(frm[0]) mod = inspect.getmodule(frm[0])
if not (mod.__name__ in ignored_modules): if not (mod.__name__ in ignored_modules):
debug_function(NOTICE, 'dbg: ' + ', '.join(u(a) for a in args)) i = ' ' * debug_indent
debug_function(NOTICE, i + 'dbg: ' + ', '.join(u(a) for a in args))
def warning(*args): def warning(*args):
if debug_function and enable_warning: if debug_function and enable_warning:
debug_function(WARNING, 'warning: ' + ', '.join(u(a) for a in args)) i = ' ' * debug_indent
debug_function(WARNING, i + 'warning: ' + ', '.join(u(a) for a in args))
def speed(name): def speed(name):
if debug_function and enable_speed: if debug_function and enable_speed:
now = time.time() now = time.time()
debug_function(SPEED, 'speed: ' + '%s %s' % (name, now - start_time)) i = ' ' * debug_indent
debug_function(SPEED, i + 'speed: ' + '%s %s' % (name, now - start_time))
def print_to_stdout(level, str_out): def print_to_stdout(level, str_out):

View File

@@ -76,13 +76,13 @@ from jedi import common
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import debug from jedi import debug
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import builtin
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.evaluate.cache import memoize_default from jedi.evaluate.cache import memoize_default
from jedi.evaluate import stdlib from jedi.evaluate import stdlib
from jedi.evaluate import finder from jedi.evaluate import finder
from jedi.evaluate import compiled
class Evaluator(object): class Evaluator(object):
@@ -128,7 +128,7 @@ class Evaluator(object):
true (default). true (default).
>>> pairs[2] #doctest: +ELLIPSIS >>> pairs[2] #doctest: +ELLIPSIS
(<Module: ...builtin...>, [<Name: ...>, ...]) (<Builtin: ...builtin...>, [<PyName: ...>, ...])
:rtype: [(pr.Scope, [pr.Name])] :rtype: [(pr.Scope, [pr.Name])]
:return: Return an generator that yields a pair of scope and names. :return: Return an generator that yields a pair of scope and names.
@@ -146,7 +146,9 @@ class Evaluator(object):
if not (scope != non_flow and scope.isinstance(pr.Class) if not (scope != non_flow and scope.isinstance(pr.Class)
or scope.isinstance(pr.Flow) or scope.isinstance(pr.Flow)
or scope.isinstance(er.Instance) or scope.isinstance(er.Instance)
and non_flow.isinstance(er.Function)): and non_flow.isinstance(er.Function)
or isinstance(scope, compiled.PyObject)
and scope.type() == 'class' and in_func_scope != scope):
try: try:
if isinstance(scope, er.Instance): if isinstance(scope, er.Instance):
for g in scope.scope_generator(): for g in scope.scope_generator():
@@ -173,8 +175,7 @@ class Evaluator(object):
# Add builtins to the global scope. # Add builtins to the global scope.
if include_builtin: if include_builtin:
builtin_scope = builtin.Builtin.scope yield compiled.builtin, compiled.builtin.get_defined_names()
yield builtin_scope, builtin_scope.get_defined_names()
def find_types(self, scope, name_str, position=None, search_global=False, def find_types(self, scope, name_str, position=None, search_global=False,
is_goto=False, resolve_decorator=True): is_goto=False, resolve_decorator=True):
@@ -194,15 +195,15 @@ class Evaluator(object):
@memoize_default(default=(), evaluator_is_first_arg=True) @memoize_default(default=(), evaluator_is_first_arg=True)
@recursion.recursion_decorator @recursion.recursion_decorator
@debug.increase_indent
def eval_statement(self, stmt, seek_name=None): def eval_statement(self, stmt, seek_name=None):
""" """
The starting point of the completion. A statement always owns a call list, The starting point of the completion. A statement always owns a call
which are the calls, that a statement does. list, which are the calls, that a statement does. In case multiple
In case multiple names are defined in the statement, `seek_name` returns names are defined in the statement, `seek_name` returns the result for
the result for this name. this name.
:param stmt: A `pr.Statement`. :param stmt: A `pr.Statement`.
:param seek_name: A string.
""" """
debug.dbg('eval_statement %s (%s)' % (stmt, seek_name)) debug.dbg('eval_statement %s (%s)' % (stmt, seek_name))
expression_list = stmt.expression_list() expression_list = stmt.expression_list()
@@ -266,6 +267,8 @@ class Evaluator(object):
er.Function, er.Class, er.Instance, iterable.ArrayInstance): er.Function, er.Class, er.Instance, iterable.ArrayInstance):
result.append(call) result.append(call)
# The string tokens are just operations (+, -, etc.) # The string tokens are just operations (+, -, etc.)
elif isinstance(call, compiled.PyObject):
result.append(call)
elif not isinstance(call, (str, unicode)): elif not isinstance(call, (str, unicode)):
if isinstance(call, pr.Call) and str(call.name) == 'if': if isinstance(call, pr.Call) and str(call.name) == 'if':
# Ternary operators. # Ternary operators.
@@ -281,8 +284,8 @@ class Evaluator(object):
result += self.eval_call(call) result += self.eval_call(call)
elif call == '*': elif call == '*':
if [r for r in result if isinstance(r, iterable.Array) if [r for r in result if isinstance(r, iterable.Array)
or isinstance(r, er.Instance) or isinstance(r, compiled.PyObject)
and str(r.name) == 'str']: and isinstance(r.obj, (str, unicode))]:
# if it is an iterable, ignore * operations # if it is an iterable, ignore * operations
next(calls_iterator) next(calls_iterator)
return set(result) return set(result)
@@ -308,16 +311,12 @@ class Evaluator(object):
else: else:
if isinstance(current, pr.NamePart): if isinstance(current, pr.NamePart):
# This is the first global lookup. # This is the first global lookup.
scopes = self.find_types(scope, current, position=position, types = self.find_types(scope, current, position=position,
search_global=True) search_global=True)
else: else:
# for pr.Literal # for pr.Literal
scopes = self.find_types(builtin.Builtin.scope, current.type_as_string()) types = [compiled.create(current.value)]
# Make instances of those number/string objects. types = imports.strip_imports(self, types)
scopes = itertools.chain.from_iterable(
self.execute(s, (current.value,)) for s in scopes
)
types = imports.strip_imports(self, scopes)
return self.follow_path(path, types, scope, position=position) return self.follow_path(path, types, scope, position=position)
@@ -332,8 +331,8 @@ class Evaluator(object):
results_new = [] results_new = []
iter_paths = itertools.tee(path, len(types)) iter_paths = itertools.tee(path, len(types))
for i, type in enumerate(types): for i, typ in enumerate(types):
fp = self._follow_path(iter_paths[i], type, call_scope, position=position) fp = self._follow_path(iter_paths[i], typ, call_scope, position=position)
if fp is not None: if fp is not None:
results_new += fp results_new += fp
else: else:
@@ -341,7 +340,7 @@ class Evaluator(object):
return types return types
return results_new return results_new
def _follow_path(self, path, type, scope, position=None): def _follow_path(self, path, typ, scope, position=None):
""" """
Uses a generator and tries to complete the path, e.g.:: Uses a generator and tries to complete the path, e.g.::
@@ -355,52 +354,58 @@ class Evaluator(object):
current = next(path) current = next(path)
except StopIteration: except StopIteration:
return None return None
debug.dbg('_follow_path: %s in scope %s' % (current, type)) debug.dbg('_follow_path: %s in scope %s' % (current, typ))
result = [] result = []
if isinstance(current, pr.Array): if isinstance(current, pr.Array):
# This must be an execution, either () or []. # This must be an execution, either () or [].
if current.type == pr.Array.LIST: if current.type == pr.Array.LIST:
if hasattr(type, 'get_index_types'): if hasattr(typ, 'get_index_types'):
result = type.get_index_types(current) result = typ.get_index_types(current)
elif current.type not in [pr.Array.DICT]: elif current.type not in [pr.Array.DICT]:
# Scope must be a class or func - make an instance or execution. # Scope must be a class or func - make an instance or execution.
debug.dbg('exe', type) result = self.execute(typ, current)
result = self.execute(type, current)
else: else:
# Curly braces are not allowed, because they make no sense. # Curly braces are not allowed, because they make no sense.
debug.warning('strange function call with {}', current, type) debug.warning('strange function call with {}', current, typ)
else: else:
# The function must not be decorated with something else. # The function must not be decorated with something else.
if type.isinstance(er.Function): if typ.isinstance(er.Function):
type = type.get_magic_method_scope() typ = typ.get_magic_function_scope()
else: else:
# This is the typical lookup while chaining things. # This is the typical lookup while chaining things.
if filter_private_variable(type, scope, current): if filter_private_variable(typ, scope, current):
return [] return []
result = imports.strip_imports(self, self.find_types(type, current, types = self.find_types(typ, current, position=position)
position=position)) result = imports.strip_imports(self, types)
return self.follow_path(path, set(result), scope, position=position) return self.follow_path(path, set(result), scope, position=position)
@debug.increase_indent
def execute(self, obj, params=(), evaluate_generator=False): def execute(self, obj, params=(), evaluate_generator=False):
if obj.isinstance(er.Function): if obj.isinstance(er.Function):
obj = obj.get_decorated_func() obj = obj.get_decorated_func()
debug.dbg('execute:', obj, params)
try: try:
return stdlib.execute(self, obj, params) return stdlib.execute(self, obj, params)
except stdlib.NotInStdLib: except stdlib.NotInStdLib:
pass pass
if obj.isinstance(er.Class): if obj.isinstance(compiled.PyObject):
if obj.is_executable_class():
return [er.Instance(self, obj, params)]
else:
return list(obj.execute_function(self, params))
elif obj.isinstance(er.Class):
# There maybe executions of executions. # There maybe executions of executions.
return [er.Instance(self, obj, params)] return [er.Instance(self, obj, params)]
elif isinstance(obj, iterable.Generator): elif isinstance(obj, iterable.Generator):
return obj.iter_content() return obj.iter_content()
else: else:
stmts = [] stmts = []
try: if obj.isinstance(er.Function):
obj.returns # Test if it is a function stmts = er.FunctionExecution(self, obj, params).get_return_types(evaluate_generator)
except AttributeError: else:
if hasattr(obj, 'execute_subscope_by_name'): if hasattr(obj, 'execute_subscope_by_name'):
try: try:
stmts = obj.execute_subscope_by_name('__call__', params) stmts = obj.execute_subscope_by_name('__call__', params)
@@ -408,10 +413,8 @@ class Evaluator(object):
debug.warning("no __call__ func available", obj) debug.warning("no __call__ func available", obj)
else: else:
debug.warning("no execution possible", obj) debug.warning("no execution possible", obj)
else:
stmts = er.FunctionExecution(self, obj, params).get_return_types(evaluate_generator)
debug.dbg('execute: %s in %s' % (stmts, self)) debug.dbg('execute result: %s in %s' % (stmts, obj))
return imports.strip_imports(self, stmts) return imports.strip_imports(self, stmts)
def goto(self, stmt, call_path=None): def goto(self, stmt, call_path=None):
@@ -450,9 +453,14 @@ def filter_private_variable(scope, call_scope, var_name):
"""private variables begin with a double underline `__`""" """private variables begin with a double underline `__`"""
if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\ if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\
and var_name.startswith('__') and not var_name.endswith('__'): and var_name.startswith('__') and not var_name.endswith('__'):
s = call_scope.get_parent_until((pr.Class, er.Instance)) s = call_scope.get_parent_until((pr.Class, er.Instance, compiled.PyObject))
if s != scope and s != scope.base.base: if s != scope:
return True if isinstance(scope.base, compiled.PyObject):
if s != scope.base:
return True
else:
if s != scope.base.base:
return True
return False return False

View File

@@ -1,455 +0,0 @@
"""
A big part of the Python standard libraries are unfortunately not only written
in Python. The process works like this:
- ``BuiltinModule`` imports the builtin module (e.g. ``sys``)
- then ``BuiltinModule`` generates code with the docstrings of builtin
functions.
- The :mod:`parsing` parser processes the generated code.
This is possible, because many builtin functions supply docstrings, for example
the method ``list.index`` has the following attribute ``__doc__``:
L.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
`PEP 257 <http://www.python.org/dev/peps/pep-0257/#one-line-docstrings>`_
teaches how docstrings should look like for C functions.
Additionally there's a ``Builtin`` instance in this module, to make it
possible to access functions like ``list`` and ``int`` directly, the same way
|jedi| access other functions.
"""
from jedi._compatibility import exec_function, is_py3k
import re
import sys
import os
if is_py3k:
import io
import types
import inspect
from jedi import common
from jedi import debug
from jedi.parser import Parser
from jedi.parser import fast
from jedi.evaluate.sys_path import get_sys_path
from jedi import cache
class BuiltinModule(object):
"""
This module is a parser for all builtin modules, which are programmed in
C/C++. It should also work on third party modules.
It can be instantiated with either a path or a name of the module. The path
is important for third party modules.
:param name: The name of the module.
:param path: The path of the module.
:param sys_path: The sys.path, which is can be customizable.
"""
map_types = {
'floating point number': '0.0',
'string': '""',
'str': '""',
'character': '"a"',
'integer': '0',
'int': '0',
'dictionary': '{}',
'list': '[]',
'file object': 'file("")',
# TODO things like dbg: ('not working', 'tuple of integers')
}
if is_py3k:
map_types['file object'] = 'import io; return io.TextIOWrapper()'
def __init__(self, path=None, name=None, sys_path=None):
if sys_path is None:
sys_path = get_sys_path()
self.sys_path = list(sys_path)
if not name:
name = os.path.basename(path)
name = name.rpartition('.')[0] # cut file type (normally .so)
self.name = name
self.path = path and os.path.abspath(path)
@property
@cache.underscore_memoization
def parser(self):
""" get the parser lazy """
return cache.load_parser(self.path, self.name) or self._load_module()
def _load_module(self):
source = _generate_code(self.module, self._load_mixins())
p = self.path or self.name
p = fast.FastParser(source, p)
cache.save_parser(self.path, self.name, p)
return p
@property
@cache.underscore_memoization
def module(self):
"""get module also lazy"""
def load_module(name, path):
if path:
self.sys_path.insert(0, path)
temp, sys.path = sys.path, self.sys_path
content = {}
try:
exec_function('import %s as module' % name, content)
module = content['module']
except AttributeError:
# use sys.modules, because you cannot access some modules
# directly. -> #59
module = sys.modules[name]
sys.path = temp
if path:
self.sys_path.pop(0)
return module
# module might already be defined
path = self.path
name = self.name
if self.path:
dot_path = []
p = self.path
# search for the builtin with the correct path
while p and p not in sys.path:
p, sep, mod = p.rpartition(os.path.sep)
dot_path.append(mod.partition('.')[0])
if p:
name = ".".join(reversed(dot_path))
path = p
else:
path = os.path.dirname(self.path)
return load_module(name, path)
def _load_mixins(self):
"""
Load 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.
"""
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
name = self.name
# sometimes there are stupid endings like `_sqlite3.cpython-32mu`
name = re.sub(r'\..*', '', name)
if name == '__builtin__' and not is_py3k:
name = 'builtins'
path = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.join(path, 'mixin', name) + '.pym') as f:
s = f.read()
except IOError:
return {}
else:
mixin_dct = process_code(s)
if is_py3k and self.name == Builtin.name:
# in the case of Py3k xrange is now range
mixin_dct['range'] = mixin_dct['xrange']
return mixin_dct
def _generate_code(scope, mixin_funcs={}, depth=0):
"""
Generate a string, which uses python syntax as an input to the Parser.
"""
def get_doc(obj, indent=False):
doc = inspect.getdoc(obj)
if doc:
doc = ('r"""\n%s\n"""\n' % doc)
if indent:
doc = common.indent_block(doc)
return doc
return ''
def is_in_base_classes(cls, name, comparison):
""" Base classes may contain the exact same object """
if name in mixin_funcs:
return False
try:
mro = cls.mro()
except TypeError:
# this happens, if cls == type
return False
for base in mro[1:]:
try:
attr = getattr(base, name)
except AttributeError:
continue
if attr == comparison:
return True
return False
def get_scope_objects(names):
"""
Looks for the names defined with dir() in an objects and divides
them into different object types.
"""
classes = {}
funcs = {}
stmts = {}
members = {}
for n in names:
try:
# this has a builtin_function_or_method
exe = getattr(scope, n)
except AttributeError:
# happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
members[n] = None
else:
if inspect.isclass(scope):
if is_in_base_classes(scope, n, exe):
continue
if inspect.isbuiltin(exe) or inspect.ismethod(exe) \
or inspect.ismethoddescriptor(exe):
funcs[n] = exe
elif inspect.isclass(exe) or inspect.ismodule(exe):
classes[n] = exe
elif inspect.ismemberdescriptor(exe):
members[n] = exe
else:
stmts[n] = exe
return classes, funcs, stmts, members
code = ''
if inspect.ismodule(scope): # generate comment where the code's from.
try:
path = scope.__file__
except AttributeError:
path = '?'
code += '# Generated module %s from %s\n' % (scope.__name__, path)
code += get_doc(scope)
# Remove some magic vars, (TODO why?)
names = set(dir(scope)) - set(['__file__', '__name__', '__doc__',
'__path__', '__package__'])
classes, funcs, stmts, members = get_scope_objects(names)
# classes
for name, cl in classes.items():
bases = (c.__name__ for c in cl.__bases__) if inspect.isclass(cl) \
else []
code += 'class %s(%s):\n' % (name, ','.join(bases))
if depth == 0:
try:
mixin = mixin_funcs[name]
except KeyError:
mixin = {}
cl_code = _generate_code(cl, mixin, depth + 1)
code += common.indent_block(cl_code)
code += '\n'
# functions
for name, func in funcs.items():
params, ret = _parse_function_doc(func)
if depth > 0:
params = 'self, ' + params
doc_str = get_doc(func, indent=True)
try:
mixin = mixin_funcs[name]
except KeyError:
# normal code generation
code += 'def %s(%s):\n' % (name, params)
code += doc_str
code += common.indent_block('%s\n\n' % ret)
else:
# generation of code with mixins
# the parser only supports basic functions with a newline after
# the double dots
# find doc_str place
try:
pos = re.search(r'\):\s*\n', mixin).end()
except TypeError:
# pypy uses a different reversed builtin
if name == 'reversed':
mixin = 'def reversed(sequence):\n' \
' for i in self.__sequence: yield i'
pos = 24
else:
debug.warning('mixin trouble in pypy: %s', name)
raise
if pos is None:
raise Exception("Builtin function not parsed correctly")
code += mixin[:pos] + doc_str + mixin[pos:]
# class members (functions) properties?
for name, func in members.items():
# recursion problem in properties TODO remove
if name in ['fget', 'fset', 'fdel']:
continue
ret = 'pass'
code += '@property\ndef %s(self):\n' % (name)
code += common.indent_block(get_doc(func) + '%s\n\n' % ret)
# variables
for name, value in stmts.items():
if is_py3k:
file_type = io.TextIOWrapper
else:
file_type = types.FileType
if isinstance(value, file_type):
value = 'open()'
elif name == 'None':
value = ''
elif type(value).__name__ in ['int', 'bool', 'float',
'dict', 'list', 'tuple']:
value = repr(value)
else:
# get the type, if the type is not simple.
mod = type(value).__module__
value = type(value).__name__ + '()'
if mod != '__builtin__':
value = '%s.%s' % (mod, value)
code += '%s = %s\n' % (name, value)
return code
def _parse_function_doc(func):
"""
Takes a function and returns the params and return value as a tuple.
This is nothing more than a docstring parser.
"""
# TODO: things like utime(path, (atime, mtime)) and a(b [, b]) -> None
doc = inspect.getdoc(func)
if doc is None:
return '', 'pass'
# get full string, parse round parentheses: def func(a, (b,c))
try:
# unbound methods such as pyqtSignals have no __name__
if not hasattr(func, "__name__"):
return '', 'pass'
count = 0
debug.dbg(func, func.__name__, doc)
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 = BuiltinModule.map_types.get(ret_str, ret_str)
if ret == ret_str and ret not in ['None', 'object', 'tuple', 'set']:
debug.dbg('not working', ret_str)
ret = ('return ' if 'return' not in ret else '') + ret
return param_str, ret
class Builtin(object):
""" The builtin scope / module """
# Python 3 compatibility
if is_py3k:
name = 'builtins'
else:
name = '__builtin__'
@property
@cache.underscore_memoization
def builtin(self):
return BuiltinModule(name=self.name)
@property
def scope(self):
return self.builtin.parser.module
def magic_function_scope(self, evaluator):
try:
return self._magic_function_scope
except AttributeError:
# depth = 1 because this is not a module
class Container(object):
FunctionType = types.FunctionType
source = _generate_code(Container, depth=0)
parser = Parser(source, None)
module = parser.module
module.parent = self.scope
typ = evaluator.follow_path(iter(['FunctionType']), [module], module)
s = self._magic_function_scope = typ.pop()
return s
Builtin = Builtin()

View File

@@ -0,0 +1,302 @@
"""
Imitate the parser representation.
"""
import inspect
import re
import sys
import os
from jedi._compatibility import builtins as _builtins, exec_function
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
class PyObject(Base):
# comply with the parser
start_pos = 0, 0
asserts = []
path = None # modules have this attribute - set it to None.
def __init__(self, obj, parent=None):
self.obj = obj
self.parent = parent
self.doc = inspect.getdoc(obj)
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.obj)
def get_parent_until(self, *args, **kwargs):
# compiled modules only use functions and classes/methods (2 levels)
return getattr(self.parent, 'parent', self.parent) or self.parent or self
@underscore_memoization
def _parse_function_doc(self):
if self.doc is None:
return '', ''
return _parse_function_doc(self.doc)
def type(self):
cls = self._cls().obj
if inspect.isclass(cls):
return 'class'
elif inspect.ismodule(cls):
return 'module'
elif inspect.isbuiltin(cls) or inspect.ismethod(cls) \
or inspect.ismethoddescriptor(cls):
return 'def'
def is_executable_class(self):
return inspect.isclass(self.obj)
@underscore_memoization
def _cls(self):
# Ensures that a PyObject is returned that is not an instance (like list)
if fake.is_class_instance(self.obj):
try:
c = self.obj.__class__
except AttributeError:
# happens with numpy.core.umath._UFUNC_API (you get it
# automatically by doing `import numpy`.
c = type(None)
return PyObject(c, self.parent)
return self
def get_defined_names(self):
cls = self._cls()
for name in dir(cls.obj):
yield PyName(cls, name)
def instance_names(self):
# TODO REMOVE (temporary until the Instance method is removed)
return self.get_defined_names()
def get_subscope_by_name(self, name):
if name in dir(self._cls().obj):
return PyName(self._cls(), name).parent
else:
raise KeyError("CompiledObject doesn't have an attribute '%s'." % name)
@property
def name(self):
# might not exist sometimes (raises AttributeError)
return self._cls().obj.__name__
def execute_function(self, evaluator, params):
if self.type() != 'def':
return
for name in self._parse_function_doc()[1].split():
try:
bltn_obj = _create_from_name(builtin, builtin, name)
except AttributeError:
continue
else:
if isinstance(bltn_obj, PyObject):
yield bltn_obj
else:
for result in evaluator.execute(bltn_obj, params):
yield result
@property
@underscore_memoization
def subscopes(self):
"""
Returns only the faked scopes - the other ones are not important for
internal analysis.
"""
module = self.get_parent_until()
faked_subscopes = []
for name in dir(self._cls().obj):
f = fake.get_faked(module.obj, self.obj, name)
if f:
f.parent = self
faked_subscopes.append(f)
return faked_subscopes
def get_self_attributes(self):
return [] # Instance compatibility
def get_imports(self):
return [] # Builtins don't have imports
class PyName(object):
def __init__(self, obj, name):
self._obj = obj
self.name = name
self.start_pos = 0, 0 # an illegal start_pos, to make sorting easy.
def get_parent_until(self):
return self.parent.get_parent_until()
def __str__(self):
return self.name
def __repr__(self):
return '<%s: (%s).%s>' % (type(self).__name__, self._obj.name, self.name)
@property
@underscore_memoization
def parent(self):
module = self._obj.get_parent_until()
return _create_from_name(module, self._obj, self.name)
@property
def names(self):
return [self.name] # compatibility with parser.representation.Name
def get_code(self):
return self.name
def load_module(path, name):
if not name:
name = os.path.basename(path)
name = name.rpartition('.')[0] # cut file type (normally .so)
# sometimes there are endings like `_sqlite3.cpython-32mu`
name = re.sub(r'\..*', '', name)
if path:
dot_path = []
p = path
# if path is not in sys.path, we need to make a well defined import
# like `from numpy.core import umath.`
while p and p not in sys.path:
p, sep, mod = p.rpartition(os.path.sep)
dot_path.insert(0, mod.partition('.')[0])
if p:
name = ".".join(dot_path)
path = p
else:
path = os.path.dirname(path)
sys_path = get_sys_path()
if path:
sys_path.insert(0, path)
temp, sys.path = sys.path, sys_path
content = {}
try:
exec_function('import %s as module' % name, content)
module = content['module']
except AttributeError:
# use sys.modules, because you cannot access some modules
# directly. -> github issue #59
module = sys.modules[name]
sys.path = temp
return PyObject(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
class Builtin(PyObject):
def get_defined_names(self):
# Filter None, because it's really just a keyword, nobody wants to
# access it.
return [d for d in super(Builtin, self).get_defined_names() if d.name != 'None']
builtin = Builtin(_builtins)
magic_function_class = PyObject(type(load_module), parent=builtin)
def _create_from_name(module, parent, name):
faked = fake.get_faked(module.obj, parent.obj, name)
# only functions are necessary.
if faked is not None:
faked.parent = parent
return faked
try:
obj = getattr(parent.obj, name)
except AttributeError:
# happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
obj = None
return PyObject(obj, parent)
def create(obj, parent=builtin, module=None):
"""
A very weird interface class to this module. The more options provided the
more acurate loading compiled objects is.
"""
if not inspect.ismodule(parent):
faked = fake.get_faked(module and module.obj, obj)
if faked is not None:
faked.parent = parent
return faked
return PyObject(obj, parent)

View File

@@ -0,0 +1,107 @@
"""
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 os
import inspect
from jedi._compatibility import is_py3k, builtins
from jedi.parser import Parser
from jedi.parser.representation import Class
from jedi.evaluate.helpers import FakeName
modules = {}
def _load_faked_module(module):
module_name = module.__name__
if module_name == '__builtin__' and not is_py3k:
module_name = 'builtins'
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:
modules[module_name] = None
return
module = Parser(source, module_name).module
modules[module_name] = module
if module_name == 'builtins' and not is_py3k:
# There are two implementations of `open` for either python 2/3.
# -> Rename the python2 version (`look at fake/builtins.pym`).
open_func = search_scope(module, 'open')
open_func.name = FakeName('open_python3')
open_func = search_scope(module, 'open_python2')
open_func.name = FakeName('open')
return module
def search_scope(scope, obj_name):
for s in scope.subscopes:
if str(s.name) == obj_name:
return s
def _faked(module, obj, name):
# Crazy underscore actions to try to escape all the internal madness.
if module is None:
try:
module = obj.__objclass__
except AttributeError:
pass
try:
imp_plz = obj.__module__
except AttributeError:
# Unfortunately in some cases like `int` there's no __module__
module = builtins
else:
module = __import__(imp_plz)
faked_mod = _load_faked_module(module)
if faked_mod is None:
return
# Having the module as a `parser.representation.module`, we need to scan
# for methods.
if name is None:
if inspect.isbuiltin(obj):
return search_scope(faked_mod, obj.__name__)
elif not inspect.isclass(obj):
# object is a method or descriptor
cls = search_scope(faked_mod, obj.__objclass__.__name__)
if cls is None:
return
return search_scope(cls, obj.__name__)
else:
if obj == module:
return search_scope(faked_mod, name)
else:
cls = search_scope(faked_mod, obj.__name__)
if cls is None:
return
return search_scope(cls, name)
def get_faked(module, obj, name=None):
obj = obj.__class__ if is_class_instance(obj) else obj
result = _faked(module, obj, name)
if not isinstance(result, Class) and result is not None:
# Set the docstr which was previously not set (faked modules don't
# contain it).
result.docstr = obj.__doc__ or ''
return result
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))

View File

@@ -1,7 +1,7 @@
def proxy(object, callback=None): def proxy(object, callback=None):
return object return object
class ref(): class weakref():
def __init__(self, object, callback=None): def __init__(self, object, callback=None):
self.__object = object self.__object = object
def __call__(self): def __call__(self):

View File

@@ -45,6 +45,15 @@ class xrange():
return 1 return 1
def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True):
import io
return io.TextIOWrapper(file, mode, buffering, encoding, errors, newline, closefd)
def open_python2(name, mode=None, buffering=None):
return file(name, mode, buffering)
#-------------------------------------------------------- #--------------------------------------------------------
# descriptors # descriptors
#-------------------------------------------------------- #--------------------------------------------------------

View File

@@ -1,3 +1,3 @@
class TextIOWrapper(): class TextIOWrapper():
def __next__(self): def __next__(self):
return '' return 'hacked io return'

View File

@@ -7,7 +7,7 @@ from jedi import common
from jedi import settings from jedi import settings
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import dynamic from jedi.evaluate import dynamic
from jedi.evaluate import builtin from jedi.evaluate import compiled
from jedi.evaluate import docstrings from jedi.evaluate import docstrings
from jedi.evaluate import iterable from jedi.evaluate import iterable
@@ -71,9 +71,10 @@ class NameFinder(object):
if not result and isinstance(self.scope, er.Instance): if not result and isinstance(self.scope, er.Instance):
# __getattr__ / __getattribute__ # __getattr__ / __getattribute__
for r in self._check_getattr(self.scope): for r in self._check_getattr(self.scope):
new_name = copy.copy(r.name) if not isinstance(r, compiled.PyObject):
new_name.parent = r new_name = copy.copy(r.name)
result.append(new_name) new_name.parent = r
result.append(new_name)
debug.dbg('sfn filter "%s" in (%s-%s): %s@%s' debug.dbg('sfn filter "%s" in (%s-%s): %s@%s'
% (self.name_str, self.scope, nscope, u(result), self.position)) % (self.name_str, self.scope, nscope, u(result), self.position))
@@ -82,9 +83,8 @@ class NameFinder(object):
def _check_getattr(self, inst): def _check_getattr(self, inst):
"""Checks for both __getattr__ and __getattribute__ methods""" """Checks for both __getattr__ and __getattribute__ methods"""
result = [] result = []
module = builtin.Builtin.scope
# str is important to lose the NamePart! # str is important to lose the NamePart!
name = pr.String(module, "'%s'" % self.name_str, (0, 0), (0, 0), inst) name = compiled.create(str(self.name_str))
with common.ignored(KeyError): with common.ignored(KeyError):
result = inst.execute_subscope_by_name('__getattr__', [name]) result = inst.execute_subscope_by_name('__getattr__', [name])
if not result: if not result:
@@ -242,7 +242,8 @@ class NameFinder(object):
c = r.expression_list()[0] c = r.expression_list()[0]
if c in ('*', '**'): if c in ('*', '**'):
t = 'tuple' if c == '*' else 'dict' t = 'tuple' if c == '*' else 'dict'
res_new = evaluator.execute(evaluator.find_types(builtin.Builtin.scope, t)[0]) typ = evaluator.find_types(compiled.builtin, t)[0]
res_new = evaluator.execute(typ)
if not r.assignment_details: if not r.assignment_details:
# this means that there are no default params, # this means that there are no default params,
# so just ignore it. # so just ignore it.

View File

@@ -156,3 +156,20 @@ def scan_statement_for_calls(stmt, search_name, assignment_details=False):
s_new = s_new.next s_new = s_new.next
return result return result
class FakeSubModule():
line_offset = 0
class FakeStatement(pr.Statement):
def __init__(self, expression_list):
p = 0, 0
super(FakeStatement, self).__init__(FakeSubModule, expression_list, p, p)
self._expression_list = expression_list
class FakeName(pr.Name):
def __init__(self, name, parent=None):
p = 0, 0
super(FakeName, self).__init__(FakeSubModule, [(name, p)], p, p, parent)

View File

@@ -25,6 +25,7 @@ from jedi.parser import representation as pr
from jedi.evaluate import sys_path from jedi.evaluate import sys_path
from jedi import settings from jedi import settings
from jedi.common import source_to_unicode from jedi.common import source_to_unicode
from jedi.evaluate import compiled
class ModuleNotFound(Exception): class ModuleNotFound(Exception):
@@ -381,8 +382,9 @@ def strip_imports(evaluator, scopes):
@cache.cache_star_import @cache.cache_star_import
def remove_star_imports(evaluator, scope, ignored_modules=()): def remove_star_imports(evaluator, scope, ignored_modules=()):
""" """
Check a module for star imports: Check a module for star imports::
>>> from module import *
from module import *
and follow these modules. and follow these modules.
""" """
@@ -404,9 +406,7 @@ def load_module(path=None, source=None, name=None):
with open(path) as f: with open(path) as f:
source = f.read() source = f.read()
else: else:
# TODO refactoring remove return compiled.load_module(path, name)
from jedi.evaluate import builtin
return builtin.BuiltinModule(path, name).parser.module
p = path or name p = path or name
p = fast.FastParser(common.source_to_unicode(source), p) p = fast.FastParser(common.source_to_unicode(source), p)
cache.save_parser(path, name, p) cache.save_parser(path, name, p)
@@ -436,7 +436,7 @@ def get_modules_containing_name(mods, name):
return load_module(path, source) return load_module(path, source)
# skip non python modules # skip non python modules
mods = set(m for m in mods if m.path is None or m.path.endswith('.py')) mods = set(m for m in mods if not isinstance(m, compiled.PyObject))
mod_paths = set() mod_paths = set()
for m in mods: for m in mods:
mod_paths.add(m.path) mod_paths.add(m.path)

View File

@@ -3,9 +3,9 @@ import itertools
from jedi import common from jedi import common
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi._compatibility import use_metaclass, is_py3k from jedi._compatibility import use_metaclass, is_py3k, unicode
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi.evaluate import builtin from jedi.evaluate import compiled
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate.cache import CachedMetaClass, memoize_default from jedi.evaluate.cache import CachedMetaClass, memoize_default
@@ -24,16 +24,10 @@ class Generator(use_metaclass(CachedMetaClass, pr.Base)):
content of a generator. content of a generator.
""" """
names = [] names = []
none_pos = (0, 0)
executes_generator = ('__next__', 'send') executes_generator = ('__next__', 'send')
for n in ('close', 'throw') + executes_generator: for n in ('close', 'throw') + executes_generator:
name = pr.Name(builtin.Builtin.scope, [(n, none_pos)], parent = self if n in executes_generator else compiled.builtin
none_pos, none_pos) names.append(helpers.FakeName(n, parent))
if n in executes_generator:
name.parent = self
else:
name.parent = builtin.Builtin.scope
names.append(name)
debug.dbg('generator names', names) debug.dbg('generator names', names)
return names return names
@@ -78,16 +72,10 @@ class Array(use_metaclass(CachedMetaClass, pr.Base)):
# This is indexing only one element, with a fixed index number, # This is indexing only one element, with a fixed index number,
# otherwise it just ignores the index (e.g. [1+1]). # otherwise it just ignores the index (e.g. [1+1]).
index = index_possibilities[0] index = index_possibilities[0]
if isinstance(index, compiled.PyObject) \
from jedi.evaluate.representation import Instance and isinstance(index.obj, (int, str, unicode)):
if isinstance(index, Instance) \ with common.ignored(KeyError, IndexError, TypeError):
and str(index.name) in ['int', 'str'] \ return self.get_exact_index_types(index.obj)
and len(index.var_args) == 1:
# TODO this is just very hackish and a lot of use cases are
# being ignored
with common.ignored(KeyError, IndexError,
UnboundLocalError, TypeError):
return self.get_exact_index_types(index.var_args[0])
result = list(self._follow_values(self._array.values)) result = list(self._follow_values(self._array.values))
result += check_array_additions(self._evaluator, self) result += check_array_additions(self._evaluator, self)
@@ -108,6 +96,8 @@ class Array(use_metaclass(CachedMetaClass, pr.Base)):
str_key = key.value str_key = key.value
elif isinstance(key, pr.Name): elif isinstance(key, pr.Name):
str_key = str(key) str_key = str(key)
else:
continue
if mixed_index == str_key: if mixed_index == str_key:
index = i index = i
@@ -130,17 +120,17 @@ class Array(use_metaclass(CachedMetaClass, pr.Base)):
It returns e.g. for a list: append, pop, ... It returns e.g. for a list: append, pop, ...
""" """
# `array.type` is a string with the type, e.g. 'list'. # `array.type` is a string with the type, e.g. 'list'.
scope = self._evaluator.find_types(builtin.Builtin.scope, self._array.type)[0] scope = self._evaluator.find_types(compiled.builtin, self._array.type)[0]
scope = self._evaluator.execute(scope)[0] # builtins only have one class scope = self._evaluator.execute(scope)[0] # builtins only have one class
names = scope.get_defined_names() names = scope.get_defined_names()
return [ArrayMethod(n) for n in names] return [ArrayMethod(n) for n in names]
@property @property
def parent(self): def parent(self):
return builtin.Builtin.scope return compiled.builtin
def get_parent_until(self): def get_parent_until(self):
return builtin.Builtin.scope return compiled.builtin
def __getattr__(self, name): def __getattr__(self, name):
if name not in ['type', 'start_pos', 'get_only_subelement', 'parent', if name not in ['type', 'start_pos', 'get_only_subelement', 'parent',
@@ -177,7 +167,7 @@ class ArrayMethod(object):
return getattr(self.name, name) return getattr(self.name, name)
def get_parent_until(self): def get_parent_until(self):
return builtin.Builtin.scope return compiled.builtin
def __repr__(self): def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.name) return "<%s of %s>" % (type(self).__name__, self.name)
@@ -240,7 +230,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
>>> a = [""] >>> a = [""]
>>> a.append(1) >>> a.append(1)
""" """
if not settings.dynamic_array_additions or module.is_builtin(): if not settings.dynamic_array_additions or isinstance(module, compiled.PyObject):
return [] return []
def check_calls(calls, add_name): def check_calls(calls, add_name):

View File

@@ -2,8 +2,8 @@ import copy
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.evaluate import builtin
from jedi.evaluate import common from jedi.evaluate import common
from jedi.evaluate import helpers
def get_params(evaluator, func, var_args): def get_params(evaluator, func, var_args):
@@ -24,11 +24,11 @@ def get_params(evaluator, func, var_args):
new_param.parent = parent new_param.parent = parent
# create an Array (-> needed for *args/**kwargs tuples/dicts) # create an Array (-> needed for *args/**kwargs tuples/dicts)
arr = pr.Array(_FakeSubModule, start_pos, array_type, parent) arr = pr.Array(helpers.FakeSubModule, start_pos, array_type, parent)
arr.values = values arr.values = values
key_stmts = [] key_stmts = []
for key in keys: for key in keys:
stmt = pr.Statement(_FakeSubModule, [], start_pos, None) stmt = pr.Statement(helpers.FakeSubModule, [], start_pos, None)
stmt._expression_list = [key] stmt._expression_list = [key]
key_stmts.append(stmt) key_stmts.append(stmt)
arr.keys = key_stmts arr.keys = key_stmts
@@ -140,8 +140,7 @@ def _var_args_iterator(evaluator, var_args):
continue continue
old = stmt old = stmt
# generate a statement if it's not already one. # generate a statement if it's not already one.
module = builtin.Builtin.scope stmt = pr.Statement(helpers.FakeSubModule, [], (0, 0), None)
stmt = pr.Statement(module, [], (0, 0), None)
stmt._expression_list = [old] stmt._expression_list = [old]
# *args # *args
@@ -156,7 +155,7 @@ def _var_args_iterator(evaluator, var_args):
yield None, field_stmt yield None, field_stmt
elif isinstance(array, iterable.Generator): elif isinstance(array, iterable.Generator):
for field_stmt in array.iter_content(): for field_stmt in array.iter_content():
yield None, _FakeStatement(field_stmt) yield None, helpers.FakeStatement([field_stmt])
# **kwargs # **kwargs
elif expression_list[0] == '**': elif expression_list[0] == '**':
for array in evaluator.eval_expression_list(expression_list[1:]): for array in evaluator.eval_expression_list(expression_list[1:]):
@@ -177,13 +176,3 @@ def _var_args_iterator(evaluator, var_args):
yield key_arr[0].name, stmt yield key_arr[0].name, stmt
else: else:
yield None, stmt yield None, stmt
class _FakeSubModule():
line_offset = 0
class _FakeStatement(pr.Statement):
def __init__(self, content):
p = 0, 0
super(_FakeStatement, self).__init__(_FakeSubModule, [content], p, p)

View File

@@ -10,7 +10,7 @@ calls.
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi.evaluate import builtin from jedi.evaluate import compiled
from jedi.evaluate import iterable from jedi.evaluate import iterable
@@ -82,7 +82,7 @@ class _RecursionNode(object):
# The same's true for the builtins, because the builtins are really # The same's true for the builtins, because the builtins are really
# simple. # simple.
self.is_ignored = isinstance(stmt, pr.Param) \ self.is_ignored = isinstance(stmt, pr.Param) \
or (self.script == builtin.Builtin.scope) or (self.script == compiled.builtin)
def __eq__(self, other): def __eq__(self, other):
if not other: if not other:
@@ -148,7 +148,7 @@ class ExecutionRecursionDetector(object):
if isinstance(execution.base, (iterable.Array, iterable.Generator)): if isinstance(execution.base, (iterable.Array, iterable.Generator)):
return False return False
module = execution.get_parent_until() module = execution.get_parent_until()
if evaluate_generator or module == builtin.Builtin.scope: if evaluate_generator or module == compiled.builtin:
return False return False
if in_par_execution_funcs: if in_par_execution_funcs:

View File

@@ -16,7 +16,7 @@ from jedi.parser import representation as pr
from jedi import debug from jedi import debug
from jedi import common from jedi import common
from jedi.evaluate.cache import memoize_default, CachedMetaClass from jedi.evaluate.cache import memoize_default, CachedMetaClass
from jedi.evaluate import builtin from jedi.evaluate import compiled
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.evaluate import docstrings from jedi.evaluate import docstrings
@@ -49,7 +49,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
def __init__(self, evaluator, base, var_args=()): def __init__(self, evaluator, base, var_args=()):
super(Instance, self).__init__(evaluator, base, var_args) super(Instance, self).__init__(evaluator, base, var_args)
if str(base.name) in ['list', 'set'] \ if str(base.name) in ['list', 'set'] \
and builtin.Builtin.scope == base.get_parent_until(): and compiled.builtin == base.get_parent_until():
# compare the module path with the builtin name. # compare the module path with the builtin name.
self.var_args = iterable.check_array_instances(evaluator, self) self.var_args = iterable.check_array_instances(evaluator, self)
else: else:
@@ -113,9 +113,10 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
if n.names[0] == self_name and len(n.names) == 2: if n.names[0] == self_name and len(n.names) == 2:
add_self_dot_name(n) add_self_dot_name(n)
for s in self.base.get_super_classes(): if not isinstance(self.base, compiled.PyObject):
for inst in self._evaluator.execute(s): for s in self.base.get_super_classes():
names += inst.get_self_attributes() for inst in self._evaluator.execute(s):
names += inst.get_self_attributes()
return names return names
def get_subscope_by_name(self, name): def get_subscope_by_name(self, name):
@@ -141,8 +142,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
""" """
names = self.get_self_attributes() names = self.get_self_attributes()
class_names = self.base.instance_names() for var in self.base.instance_names():
for var in class_names:
names.append(InstanceElement(self._evaluator, self, var, True)) names.append(InstanceElement(self._evaluator, self, var, True))
return names return names
@@ -154,8 +154,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)):
yield self, self.get_self_attributes() yield self, self.get_self_attributes()
names = [] names = []
class_names = self.base.instance_names() for var in self.base.instance_names():
for var in class_names:
names.append(InstanceElement(self._evaluator, self, var, True)) names.append(InstanceElement(self._evaluator, self, var, True))
yield self, names yield self, names
@@ -199,10 +198,10 @@ class InstanceElement(use_metaclass(CachedMetaClass, pr.Base)):
def parent(self): def parent(self):
par = self.var.parent par = self.var.parent
if isinstance(par, Class) and par == self.instance.base \ if isinstance(par, Class) and par == self.instance.base \
or isinstance(par, pr.Class) \ or isinstance(par, pr.Class) \
and par == self.instance.base.base: and par == self.instance.base.base:
par = self.instance par = self.instance
elif not isinstance(par, pr.Module): elif not isinstance(par, (pr.Module, compiled.PyObject)):
par = InstanceElement(self.instance._evaluator, self.instance, par, self.is_class_var) par = InstanceElement(self.instance._evaluator, self.instance, par, self.is_class_var)
return par return par
@@ -256,13 +255,14 @@ class Class(use_metaclass(CachedMetaClass, pr.IsScope)):
debug.warning('Received non class, as a super class') debug.warning('Received non class, as a super class')
continue # Just ignore other stuff (user input error). continue # Just ignore other stuff (user input error).
supers.append(cls) supers.append(cls)
if not supers and self.base.parent != builtin.Builtin.scope: if not supers and self.base.parent != compiled.builtin:
# add `object` to classes # add `object` to classes
supers += self._evaluator.find_types(builtin.Builtin.scope, 'object') supers += self._evaluator.find_types(compiled.builtin, 'object')
return supers return supers
@memoize_default(default=()) @memoize_default(default=())
def instance_names(self): def instance_names(self):
# TODO REMOVE instance_names
def in_iterable(name, iterable): def in_iterable(name, iterable):
""" checks if the name is in the variable 'iterable'. """ """ checks if the name is in the variable 'iterable'. """
for i in iterable: for i in iterable:
@@ -277,17 +277,20 @@ class Class(use_metaclass(CachedMetaClass, pr.IsScope)):
# TODO mro! # TODO mro!
for cls in self.get_super_classes(): for cls in self.get_super_classes():
# Get the inherited names. # Get the inherited names.
for i in cls.instance_names(): if isinstance(cls, compiled.PyObject):
if not in_iterable(i, result): super_result += cls.get_defined_names()
super_result.append(i) else:
for i in cls.instance_names():
if not in_iterable(i, result):
super_result.append(i)
result += super_result result += super_result
return result return result
@memoize_default(default=()) @memoize_default(default=())
def get_defined_names(self): def get_defined_names(self):
result = self.instance_names() result = self.instance_names()
type_cls = self._evaluator.find_types(builtin.Builtin.scope, 'type')[0] type_cls = self._evaluator.find_types(compiled.builtin, 'type')[0]
return result + type_cls.base.get_defined_names() return result + list(type_cls.get_defined_names())
def get_subscope_by_name(self, name): def get_subscope_by_name(self, name):
for sub in reversed(self.subscopes): for sub in reversed(self.subscopes):
@@ -373,11 +376,11 @@ class Function(use_metaclass(CachedMetaClass, pr.IsScope)):
return Function(self._evaluator, self.base_func, True) return Function(self._evaluator, self.base_func, True)
return decorated_func return decorated_func
def get_magic_method_names(self): def get_magic_function_names(self):
return builtin.Builtin.magic_function_scope(self._evaluator).get_defined_names() return compiled.magic_function_class.get_defined_names()
def get_magic_method_scope(self): def get_magic_function_scope(self):
return builtin.Builtin.magic_function_scope(self._evaluator) return compiled.magic_function_class
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.base_func, name) return getattr(self.base_func, name)

View File

@@ -3,7 +3,8 @@ Implementations of standard library functions, because it's not possible to
understand them with Jedi. understand them with Jedi.
""" """
from jedi.evaluate import builtin from jedi._compatibility import unicode
from jedi.evaluate import compiled
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.parser import representation as pr from jedi.parser import representation as pr
@@ -17,7 +18,7 @@ class NotInStdLib(LookupError):
def execute(evaluator, obj, params): def execute(evaluator, obj, params):
if not isinstance(obj, (iterable.Generator, iterable.Array)): if not isinstance(obj, (iterable.Generator, iterable.Array)):
obj_name = str(obj.name) obj_name = str(obj.name)
if obj.parent == builtin.Builtin.scope: if obj.parent == compiled.builtin:
# for now we just support builtin functions. # for now we just support builtin functions.
try: try:
return _implemented['builtins'][obj_name](evaluator, obj, params) return _implemented['builtins'][obj_name](evaluator, obj, params)
@@ -44,18 +45,17 @@ def builtins_getattr(evaluator, obj, params):
objects = _follow_param(evaluator, params, 0) objects = _follow_param(evaluator, params, 0)
names = _follow_param(evaluator, params, 1) names = _follow_param(evaluator, params, 1)
for obj in objects: for obj in objects:
if not isinstance(obj, (er.Instance, er.Class, pr.Module)): if not isinstance(obj, (er.Instance, er.Class, pr.Module, compiled.PyObject)):
debug.warning('getattr called without instance') debug.warning('getattr called without instance')
continue continue
for arr_name in names: for name in names:
if not isinstance(arr_name, er.Instance): s = unicode, str
if isinstance(name, compiled.PyObject) and isinstance(name.obj, s):
stmts += evaluator.follow_path(iter([name.obj]), [obj], obj)
else:
debug.warning('getattr called without str') debug.warning('getattr called without str')
continue continue
if len(arr_name.var_args) != 1:
debug.warning('jedi getattr is too simple')
key = arr_name.var_args[0]
stmts += evaluator.follow_path(iter([key]), [obj], obj)
return stmts return stmts

View File

@@ -2,9 +2,8 @@ import pydoc
import keyword import keyword
from jedi._compatibility import is_py3k from jedi._compatibility import is_py3k
from jedi.parser import representation as pr
from jedi import common from jedi import common
from jedi.evaluate import builtin from jedi.evaluate import compiled
try: try:
from pydoc_data import topics as pydoc_topics from pydoc_data import topics as pydoc_topics
@@ -30,8 +29,7 @@ def keyword_names(*args, **kwargs):
kwds = [] kwds = []
for k in keywords(*args, **kwargs): for k in keywords(*args, **kwargs):
start = k.start_pos start = k.start_pos
end = start[0], start[1] + len(k.name) kwds.append(KeywordName(k, k.name, start))
kwds.append(pr.Name(k.parent, [(k.name, start)], start, end, k))
return kwds return kwds
@@ -39,11 +37,22 @@ def get_operator(string, pos):
return Keyword(string, pos) return Keyword(string, pos)
class KeywordName(object):
def __init__(self, parent, name, start_pos):
self.parent = parent
self.names = [name]
self.start_pos = start_pos
@property
def end_pos(self):
return self.start_pos[0], self.start_pos[1] + len(self.name)
class Keyword(object): class Keyword(object):
def __init__(self, name, pos): def __init__(self, name, pos):
self.name = name self.name = name
self.start_pos = pos self.start_pos = pos
self.parent = builtin.Builtin.scope self.parent = compiled.builtin
def get_parent_until(self): def get_parent_until(self):
return self.parent return self.parent

View File

@@ -362,17 +362,13 @@ class SubModule(Scope, Module):
string = '' # no path -> empty name string = '' # no path -> empty name
else: else:
sep = (re.escape(os.path.sep),) * 2 sep = (re.escape(os.path.sep),) * 2
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self.path)
self.path)
# remove PEP 3149 names # remove PEP 3149 names
string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
# positions are not real therefore choose (0, 0) # positions are not real therefore choose (0, 0)
names = [(string, (0, 0))] names = [(string, (0, 0))]
return Name(self, names, (0, 0), (0, 0), self.use_as_parent) return Name(self, names, (0, 0), (0, 0), self.use_as_parent)
def is_builtin(self):
return not (self.path is None or self.path.endswith('.py'))
@property @property
def has_explicit_absolute_import(self): def has_explicit_absolute_import(self):
""" """
@@ -1239,9 +1235,6 @@ class Literal(StatementElement):
def get_code(self): def get_code(self):
return self.literal + super(Literal, self).get_code() return self.literal + super(Literal, self).get_code()
def type_as_string(self):
return type(self.value).__name__
def __repr__(self): def __repr__(self):
if is_py3k: if is_py3k:
s = self.literal s = self.literal

View File

@@ -2,7 +2,7 @@
addopts = --doctest-modules addopts = --doctest-modules
# Ignore broken files in blackbox test directories # Ignore broken files in blackbox test directories
norecursedirs = .* docs completion refactor absolute_import namespace_package norecursedirs = .* docs completion refactor absolute_import namespace_package scripts
# Activate `clean_jedi_cache` fixture for all tests. This should be # Activate `clean_jedi_cache` fixture for all tests. This should be
# fine as long as we are using `clean_jedi_cache` as a session scoped # fine as long as we are using `clean_jedi_cache` as a session scoped

53
scripts/memory_check.py Normal file
View File

@@ -0,0 +1,53 @@
"""
This is a convenience script to test the speed and memory usage of Jedi with
large libraries.
Each library is preloaded by jedi, recording the time and memory consumed by
each operation.
You can provide additional libraries via command line arguments.
Note: This requires the psutil library, available on PyPI.
"""
import time
import sys
import psutil
import jedi
def used_memory():
"""Return the total MB of System Memory in use."""
return psutil.virtual_memory().used / 2**20
def profile_preload(mod):
"""Preload a module into Jedi, recording time and memory used."""
base = used_memory()
t0 = time.time()
jedi.preload_module(mod)
elapsed = time.time() - t0
used = used_memory() - base
return elapsed, used
def main(mods):
"""Preload the modules, and print the time and memory used."""
t0 = time.time()
baseline = used_memory()
print('Time (s) | Mem (MB) | Package')
print('------------------------------')
for mod in mods:
elapsed, used = profile_preload(mod)
if used > 0:
print('%8.1f | %8d | %s' % (elapsed, used, mod))
print('------------------------------')
elapsed = time.time() - t0
used = used_memory() - baseline
print('%8.1f | %8d | %s' % (elapsed, used, 'Total'))
if __name__ == '__main__':
mods = ['re', 'numpy', 'scipy', 'scipy.sparse', 'scipy.stats',
'wx', 'decimal', 'PyQt4.QtGui', 'PySide.QtGui', 'Tkinter']
mods += sys.argv[1:]
main(mods)

View File

@@ -26,8 +26,8 @@ setup(name='jedi',
license='MIT', license='MIT',
keywords='python completion refactoring vim', keywords='python completion refactoring vim',
long_description=readme, long_description=readme,
packages=['jedi', 'jedi.parser', 'jedi.evaluate', 'jedi.api'], packages=['jedi', 'jedi.parser', 'jedi.evaluate', 'jedi.evaluate.compiled', 'jedi.api'],
package_data={'jedi': ['evlaluate/evaluate/mixin/*.pym']}, package_data={'jedi': ['evlaluate/evaluate/compiled/fake/*.pym']},
platforms=['any'], platforms=['any'],
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',

View File

@@ -2,27 +2,25 @@
Test all things related to the ``jedi.api`` module. Test all things related to the ``jedi.api`` module.
""" """
from jedi import common, api from jedi import api
from pytest import raises from pytest import raises
def test_preload_modules(): def test_preload_modules():
def check_loaded(*modules): def check_loaded(*modules):
# + 1 for builtin, +1 for None module (currently used) # +1 for None module (currently used)
assert len(new) == len(modules) + 2 assert len(parser_cache) == len(modules) + 1
for i in modules + ('__builtin__',): for i in modules:
assert [i in k for k in new.keys() if k is not None] assert [i in k for k in parser_cache.keys() if k is not None]
from jedi import cache from jedi import cache
temp_cache, cache.parser_cache = cache.parser_cache, {} temp_cache, cache.parser_cache = cache.parser_cache, {}
new = cache.parser_cache parser_cache = cache.parser_cache
with common.ignored(KeyError): # performance of tests -> no reload
new['__builtin__'] = temp_cache['__builtin__']
api.preload_module('datetime') api.preload_module('sys')
check_loaded('datetime') check_loaded() # compiled (c_builtin) modules shouldn't be in the cache.
api.preload_module('json', 'token') api.preload_module('json', 'token')
check_loaded('datetime', 'json', 'token') check_loaded('json', 'token')
cache.parser_cache = temp_cache cache.parser_cache = temp_cache
@@ -30,6 +28,7 @@ def test_preload_modules():
def test_empty_script(): def test_empty_script():
assert api.Script('') assert api.Script('')
def test_line_number_errors(): def test_line_number_errors():
""" """
Script should raise a ValueError if line/column numbers are not in a Script should raise a ValueError if line/column numbers are not in a

View File

@@ -15,6 +15,7 @@ def test_is_keyword():
results = Script('str', 1, 1, None).goto_definitions() results = Script('str', 1, 1, None).goto_definitions()
assert len(results) == 1 and results[0].is_keyword is False assert len(results) == 1 and results[0].is_keyword is False
def make_definitions(): def make_definitions():
""" """
Return a list of definitions for parametrized tests. Return a list of definitions for parametrized tests.

View File

@@ -1,13 +0,0 @@
from jedi.evaluate import builtin
def test_parse_function_doc_illegal_docstr():
def test_func(a):
"""
test_func(o
doesn't have a closing bracket.
"""
assert ('', '') == builtin._parse_function_doc(test_func)

View File

@@ -15,7 +15,7 @@ class TestCallSignatures(TestCase):
assert signatures[0].call_name == expected_name assert signatures[0].call_name == expected_name
assert signatures[0].index == expected_index assert signatures[0].index == expected_index
def test_call_signatures(self): def test_simple(self):
def run(source, name, index=0, column=None, line=1): def run(source, name, index=0, column=None, line=1):
self._run(source, name, index, line, column) self._run(source, name, index, line, column)
@@ -72,7 +72,7 @@ class TestCallSignatures(TestCase):
"func(alpha='101'," "func(alpha='101',"
run(s, 'func', 0, column=13, line=2) run(s, 'func', 0, column=13, line=2)
def test_function_definition_complex(self): def test_complex(self):
s = """ s = """
def abc(a,b): def abc(a,b):
pass pass
@@ -106,7 +106,7 @@ class TestCallSignatures(TestCase):
# just don't throw an exception (if numpy doesn't exist, just ignore it) # just don't throw an exception (if numpy doesn't exist, just ignore it)
assert Script(s).call_signatures() == [] assert Script(s).call_signatures() == []
def test_function_definition_empty_paren_pre_space(self): def test_call_signatures_empty_parentheses_pre_space(self):
s = textwrap.dedent("""\ s = textwrap.dedent("""\
def f(a, b): def f(a, b):
pass pass

36
test/test_compiled.py Normal file
View File

@@ -0,0 +1,36 @@
from jedi._compatibility import builtins
from jedi.parser.representation import Function
from jedi.evaluate import compiled
from jedi.evaluate import Evaluator
def test_simple():
e = Evaluator()
bltn = compiled.PyObject(builtins)
obj = compiled.PyObject('_str_', bltn)
upper = e.find_types(obj, 'upper')
assert len(upper) == 1
objs = list(e.execute(upper[0]))
assert len(objs) == 1
assert objs[0].obj is str
def test_fake_loading():
assert isinstance(compiled.create(next), Function)
string = compiled.builtin.get_subscope_by_name('str')
from_name = compiled._create_from_name(compiled.builtin, string, '__init__')
assert isinstance(from_name, Function)
def test_fake_docstr():
assert compiled.create(next).docstr == next.__doc__
def test_parse_function_doc_illegal_docstr():
docstr = """
test_func(o
doesn't have a closing bracket.
"""
assert ('', '') == compiled._parse_function_doc(docstr)