1
0
forked from VimPlug/jedi

Merge branch 'dev' into bugfix/performances_degradation

# Conflicts:
#	test/test_regression.py
This commit is contained in:
ColinDuquesnoy
2016-07-30 16:52:17 +02:00
37 changed files with 647 additions and 252 deletions
+1 -1
View File
@@ -275,7 +275,7 @@ class Evaluator(object):
return self._eval_element_not_cached(element)
return self._eval_element_cached(element)
@memoize_default(evaluator_is_first_arg=True)
@memoize_default(default=set(), evaluator_is_first_arg=True)
def _eval_element_cached(self, element):
return self._eval_element_not_cached(element)
+1 -1
View File
@@ -91,7 +91,7 @@ def add(evaluator, name, jedi_obj, message=None, typ=Error, payload=None):
module_path = jedi_obj.get_parent_until().path
instance = typ(name, module_path, jedi_obj.start_pos, message)
debug.warning(str(instance))
debug.warning(str(instance), format=False)
evaluator.analysis.append(instance)
+1 -1
View File
@@ -156,7 +156,7 @@ def get_faked(module, obj, name=None):
doc = '"""%s"""' % obj.__doc__ # TODO need escapes.
suite = result.children[-1]
string = pt.String(pt.zero_position_modifier, doc, (0, 0), '')
new_line = pt.Whitespace('\n', (0, 0), '')
new_line = pt.Newline('\n', (0, 0), '')
docstr_node = pt.Node('simple_stmt', [string, new_line])
suite.children.insert(2, docstr_node)
return result
+14 -5
View File
@@ -21,6 +21,7 @@ from textwrap import dedent
from jedi.evaluate.cache import memoize_default
from jedi.parser import ParserWithRecovery, load_grammar
from jedi.parser.tree import Class
from jedi.common import indent_block
from jedi.evaluate.iterable import Array, FakeSequence, AlreadyEvaluated
@@ -174,13 +175,21 @@ def _execute_array_values(evaluator, array):
@memoize_default(None, evaluator_is_first_arg=True)
def follow_param(evaluator, param):
def eval_docstring(docstring):
return set(
[p for param_str in _search_param_in_docstr(docstring, str(param.name))
for p in _evaluate_for_statement_string(evaluator, param_str, module)]
)
func = param.parent_function
module = param.get_parent_until()
return set(
[p for param_str in _search_param_in_docstr(func.raw_doc,
str(param.name))
for p in _evaluate_for_statement_string(evaluator, param_str,
param.get_parent_until())])
types = eval_docstring(func.raw_doc)
if func.name.value == '__init__':
cls = func.get_parent_until(Class)
if cls.type == 'classdef':
types |= eval_docstring(cls.raw_doc)
return types
@memoize_default(None, evaluator_is_first_arg=True)
+2 -2
View File
@@ -26,8 +26,8 @@ def deep_ast_copy(obj, parent=None, new_elements=None):
new_children = []
for child in obj.children:
typ = child.type
if typ in ('whitespace', 'operator', 'keyword', 'number', 'string',
'indent', 'dedent', 'error_leaf'):
if typ in ('newline', 'operator', 'keyword', 'number', 'string',
'indent', 'dedent', 'endmarker', 'error_leaf'):
# At the moment we're not actually copying those primitive
# elements, because there's really no need to. The parents are
# obviously wrong, but that's not an issue.
+48 -53
View File
@@ -68,56 +68,49 @@ class ImportWrapper(tree.Base):
@memoize_default()
def follow(self, is_goto=False):
if self._evaluator.recursion_detector.push_stmt(self._import):
# check recursion
return set()
module = self._evaluator.wrap(self._import.get_parent_until())
import_path = self._import.path_for_name(self._name)
from_import_name = None
try:
module = self._evaluator.wrap(self._import.get_parent_until())
import_path = self._import.path_for_name(self._name)
from_import_name = None
try:
from_names = self._import.get_from_names()
except AttributeError:
# Is an import_name
pass
else:
if len(from_names) + 1 == len(import_path):
# We have to fetch the from_names part first and then check
# if from_names exists in the modules.
from_import_name = import_path[-1]
import_path = from_names
from_names = self._import.get_from_names()
except AttributeError:
# Is an import_name
pass
else:
if len(from_names) + 1 == len(import_path):
# We have to fetch the from_names part first and then check
# if from_names exists in the modules.
from_import_name = import_path[-1]
import_path = from_names
importer = Importer(self._evaluator, tuple(import_path),
module, self._import.level)
importer = Importer(self._evaluator, tuple(import_path),
module, self._import.level)
types = importer.follow()
types = importer.follow()
#if self._import.is_nested() and not self.nested_resolve:
# scopes = [NestedImportModule(module, self._import)]
#if self._import.is_nested() and not self.nested_resolve:
# scopes = [NestedImportModule(module, self._import)]
if from_import_name is not None:
types = set(chain.from_iterable(
self._evaluator.find_types(t, unicode(from_import_name),
is_goto=is_goto)
for t in types))
if from_import_name is not None:
types = set(chain.from_iterable(
self._evaluator.find_types(t, unicode(from_import_name),
is_goto=is_goto)
for t in types))
if not types:
path = import_path + [from_import_name]
importer = Importer(self._evaluator, tuple(path),
module, self._import.level)
types = importer.follow()
# goto only accepts `Name`
if is_goto:
types = set(s.name for s in types)
else:
if not types:
path = import_path + [from_import_name]
importer = Importer(self._evaluator, tuple(path),
module, self._import.level)
types = importer.follow()
# goto only accepts `Name`
if is_goto:
types = set(s.name for s in types)
else:
# goto only accepts `Name`
if is_goto:
types = set(s.name for s in types)
debug.dbg('after import: %s', types)
finally:
self._evaluator.recursion_detector.pop_stmt()
debug.dbg('after import: %s', types)
return types
@@ -285,20 +278,17 @@ class Importer(object):
# We can take the first element, because only the os special
# case yields multiple modules, which is not important for
# further imports.
base = list(bases)[0]
parent_module = list(bases)[0]
# This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in
# ``os``.
if [str(i) for i in import_path] == ['os', 'path']:
return self._evaluator.find_types(base, 'path')
return self._evaluator.find_types(parent_module, 'path')
try:
# It's possible that by giving it always the sys path (and not
# the __path__ attribute of the parent, we get wrong results
# and nested namespace packages don't work. But I'm not sure.
paths = base.py__path__(sys_path)
paths = parent_module.py__path__()
except AttributeError:
# The module is not a package.
_add_error(self._evaluator, import_path[-1])
@@ -318,6 +308,7 @@ class Importer(object):
_add_error(self._evaluator, import_path[-1])
return set()
else:
parent_module = None
try:
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
# Override the sys.path. It works only good that way.
@@ -337,15 +328,18 @@ class Importer(object):
if is_pkg:
# In this case, we don't have a file yet. Search for the
# __init__ file.
module_path = get_init_path(module_path)
if module_path.endswith(('.zip', '.egg')):
source = module_file.loader.get_source(module_name)
else:
module_path = get_init_path(module_path)
elif module_file:
source = module_file.read()
module_file.close()
if module_file is None and not module_path.endswith('.py'):
if module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
module = compiled.load_module(self._evaluator, module_path)
else:
module = _load_module(self._evaluator, module_path, source, sys_path)
module = _load_module(self._evaluator, module_path, source, sys_path, parent_module)
if module is None:
# The file might raise an ImportError e.g. and therefore not be
@@ -408,7 +402,7 @@ class Importer(object):
# namespace packages
if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'):
paths = scope.py__path__(self.sys_path_with_modifications())
paths = scope.py__path__()
names += self._get_module_names(paths)
if only_modules:
@@ -441,10 +435,10 @@ class Importer(object):
return names
def _load_module(evaluator, path=None, source=None, sys_path=None):
def _load_module(evaluator, path=None, source=None, sys_path=None, parent_module=None):
def load(source):
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
if path is not None and path.endswith('.py') \
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
and dotted_path not in settings.auto_import_modules:
if source is None:
with open(path, 'rb') as f:
@@ -454,7 +448,8 @@ def _load_module(evaluator, path=None, source=None, sys_path=None):
p = path
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
save_parser(path, p)
return p.module
from jedi.evaluate.representation import ModuleWrapper
return ModuleWrapper(evaluator, p.module, parent_module)
if sys_path is None:
sys_path = evaluator.sys_path
+59 -40
View File
@@ -30,6 +30,7 @@ from jedi.evaluate import helpers
from jedi.evaluate.cache import CachedMetaClass, memoize_default
from jedi.evaluate import analysis
from jedi.evaluate import pep0484
from jedi import common
class IterableWrapper(tree.Base):
@@ -180,36 +181,44 @@ class Comprehension(IterableWrapper):
"""
comp_for = self._get_comp_for()
# For nested comprehensions we need to search the last one.
from jedi.evaluate.representation import InstanceElement
node = self._get_comprehension().children[index]
if isinstance(node, InstanceElement):
# This seems to be a strange case that I haven't found a way to
# write tests against. However since it's my new goal to get rid of
# InstanceElement anyway, I don't care.
node = node.var
last_comp = list(comp_for.get_comp_fors())[-1]
return helpers.deep_ast_copy(self._get_comprehension().children[index], parent=last_comp)
@memoize_default()
def _iterate(self):
def nested(comp_fors):
comp_for = comp_fors[0]
input_node = comp_for.children[3]
input_types = evaluator.eval_element(input_node)
iterated = py__iter__(evaluator, input_types, input_node)
exprlist = comp_for.children[1]
for types in iterated:
evaluator.predefined_if_name_dict_dict[comp_for] = \
unpack_tuple_to_dict(evaluator, types, exprlist)
try:
for result in nested(comp_fors[1:]):
yield result
except IndexError:
iterated = evaluator.eval_element(self._eval_node())
if self.type == 'dict':
yield iterated, evaluator.eval_element(self._eval_node(2))
else:
yield iterated
finally:
del evaluator.predefined_if_name_dict_dict[comp_for]
return helpers.deep_ast_copy(node, parent=last_comp)
def _nested(self, comp_fors):
evaluator = self._evaluator
comp_fors = list(self._get_comp_for().get_comp_fors())
for result in nested(comp_fors):
comp_for = comp_fors[0]
input_node = comp_for.children[3]
input_types = evaluator.eval_element(input_node)
iterated = py__iter__(evaluator, input_types, input_node)
exprlist = comp_for.children[1]
for i, types in enumerate(iterated):
evaluator.predefined_if_name_dict_dict[comp_for] = \
unpack_tuple_to_dict(evaluator, types, exprlist)
try:
for result in self._nested(comp_fors[1:]):
yield result
except IndexError:
iterated = evaluator.eval_element(self._eval_node())
if self.type == 'dict':
yield iterated, evaluator.eval_element(self._eval_node(2))
else:
yield iterated
finally:
del evaluator.predefined_if_name_dict_dict[comp_for]
@memoize_default(default=[])
@common.to_list
def _iterate(self):
comp_fors = tuple(self._get_comp_for().get_comp_fors())
for result in self._nested(comp_fors):
yield result
def py__iter__(self):
@@ -252,7 +261,7 @@ class ArrayMixin(object):
@register_builtin_method('values', type='dict')
def _imitate_values(self):
items = self.dict_values()
return create_evaluated_sequence_set(self._evaluator, items, type='list')
return create_evaluated_sequence_set(self._evaluator, items, sequence_type='list')
#return set([FakeSequence(self._evaluator, [AlreadyEvaluated(items)], 'tuple')])
@register_builtin_method('items', type='dict')
@@ -260,7 +269,7 @@ class ArrayMixin(object):
items = [set([FakeSequence(self._evaluator, (k, v), 'tuple')])
for k, v in self._items()]
return create_evaluated_sequence_set(self._evaluator, *items, type='list')
return create_evaluated_sequence_set(self._evaluator, *items, sequence_type='list')
class ListComprehension(Comprehension, ArrayMixin):
@@ -268,7 +277,14 @@ class ListComprehension(Comprehension, ArrayMixin):
def py__getitem__(self, index):
all_types = list(self.py__iter__())
return all_types[index]
result = all_types[index]
if isinstance(index, slice):
return create_evaluated_sequence_set(
self._evaluator,
unite(result),
sequence_type='list'
)
return result
class SetComprehension(Comprehension, ArrayMixin):
@@ -303,9 +319,7 @@ class DictComprehension(Comprehension, ArrayMixin):
(AlreadyEvaluated(keys), AlreadyEvaluated(values)), 'tuple')
for keys, values in self._iterate())
return create_evaluated_sequence_set(self._evaluator, items, type='list')
return create_evaluated_sequence_set(self._evaluator, items, sequence_type='list')
class GeneratorComprehension(Comprehension, GeneratorMixin):
@@ -446,7 +460,9 @@ def create_evaluated_sequence_set(evaluator, *types_order, **kwargs):
``sequence_type`` is a named argument, that doesn't work in Python2. For backwards
compatibility reasons, we're now using kwargs.
"""
sequence_type = kwargs.get('sequence_type')
sequence_type = kwargs.pop('sequence_type')
assert not kwargs
sets = tuple(AlreadyEvaluated(types) for types in types_order)
return set([FakeSequence(evaluator, sets, sequence_type)])
@@ -676,7 +692,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
# Arguments([AlreadyEvaluated([_ArrayInstance])]) inside
# Yeah... I know... It's complicated ;-)
node = list(element.var_args.argument_node[0])[0].var_args.trailer
if isinstance(node, er.InstanceElement):
if isinstance(node, er.InstanceElement) or node is None:
return node
return node.get_parent_until(er.FunctionExecution)
@@ -729,11 +745,12 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
# Check for recursion. Possible by using 'extend' in
# combination with function calls.
continue
if compare_array in evaluator.eval_element(power):
# The arrays match. Now add the results
added_types |= check_additions(execution_trailer.children[1], add_name)
evaluator.recursion_detector.pop_stmt()
try:
if compare_array in evaluator.eval_element(power):
# The arrays match. Now add the results
added_types |= check_additions(execution_trailer.children[1], add_name)
finally:
evaluator.recursion_detector.pop_stmt()
# reset settings
settings.dynamic_params_for_other_modules = temp_param_add
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
@@ -777,6 +794,8 @@ class _ArrayInstance(IterableWrapper):
yield types
module = self.var_args.get_parent_until()
if module is None:
return
is_list = str(self.instance.name) == 'list'
additions = _check_array_additions(self._evaluator, self.instance, module, is_list)
if additions:
-1
View File
@@ -15,7 +15,6 @@ from jedi.evaluate import iterable
def recursion_decorator(func):
def run(evaluator, stmt, *args, **kwargs):
rec_detect = evaluator.recursion_detector
# print stmt, len(self.node_statements())
if rec_detect.push_stmt(stmt):
return set()
else:
+36 -25
View File
@@ -472,7 +472,10 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)):
@property
def params(self):
return self.get_subscope_by_name('__init__').params
try:
return self.get_subscope_by_name('__init__').params
except KeyError:
return [] # object.__init__
def names_dicts(self, search_global, is_instance=False):
if search_global:
@@ -491,7 +494,7 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)):
for s in self.py__mro__():
for sub in reversed(s.subscopes):
if sub.name.value == name:
return sub
return sub
raise KeyError("Couldn't find subscope.")
def __getattr__(self, name):
@@ -803,9 +806,10 @@ class GlobalName(helpers.FakeName):
class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
def __init__(self, evaluator, module):
def __init__(self, evaluator, module, parent_module=None):
self._evaluator = evaluator
self.base = self._module = module
self._parent_module = parent_module
def names_dicts(self, search_global):
yield self.base.names_dict
@@ -851,6 +855,10 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
return helpers.FakeName(unicode(self.base.name), self, (1, 0))
def _get_init_directory(self):
"""
:return: The path to the directory of a package. None in case it's not
a package.
"""
for suffix, _, _ in imp.get_suffixes():
ending = '__init__' + suffix
py__file__ = self.py__file__()
@@ -881,6 +889,30 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
else:
return self.py__name__()
def _py__path__(self):
if self._parent_module is None:
search_path = self._evaluator.sys_path
else:
search_path = self._parent_module.py__path__()
init_path = self.py__file__()
if os.path.basename(init_path) == '__init__.py':
with open(init_path, 'rb') as f:
content = common.source_to_unicode(f.read())
# these are strings that need to be used for namespace packages,
# the first one is ``pkgutil``, the second ``pkg_resources``.
options = ('declare_namespace(__name__)', 'extend_path(__path__')
if options[0] in content or options[1] in content:
# It is a namespace, now try to find the rest of the
# modules on sys_path or whatever the search_path is.
paths = set()
for s in search_path:
other = os.path.join(s, unicode(self.name))
if os.path.isdir(other):
paths.add(other)
return list(paths)
# Default to this.
return [self._get_init_directory()]
@property
def py__path__(self):
"""
@@ -893,33 +925,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
is a list of paths (strings).
Raises an AttributeError if the module is not a package.
"""
def return_value(search_path):
init_path = self.py__file__()
if os.path.basename(init_path) == '__init__.py':
with open(init_path, 'rb') as f:
content = common.source_to_unicode(f.read())
# these are strings that need to be used for namespace packages,
# the first one is ``pkgutil``, the second ``pkg_resources``.
options = ('declare_namespace(__name__)', 'extend_path(__path__')
if options[0] in content or options[1] in content:
# It is a namespace, now try to find the rest of the
# modules on sys_path or whatever the search_path is.
paths = set()
for s in search_path:
other = os.path.join(s, unicode(self.name))
if os.path.isdir(other):
paths.add(other)
return list(paths)
# Default to this.
return [path]
path = self._get_init_directory()
if path is None:
raise AttributeError('Only packages have __path__ attributes.')
else:
return return_value
return self._py__path__
@memoize_default()
def _sub_modules_dict(self):