Fix dynamic arrays.

This commit is contained in:
Dave Halter
2016-11-27 21:48:48 +01:00
parent 558e8add49
commit c3f6172ca2
3 changed files with 72 additions and 80 deletions

View File

@@ -50,6 +50,9 @@ class Context(object):
search_global=False, is_goto=False):
return self.evaluator.find_types(self, name_or_str, position, search_global, is_goto)
def create_context(self, node):
return self.evaluator.create_context(self, node)
class TreeContext(Context):
def __init__(self, evaluator, parent_context=None):

View File

@@ -7,19 +7,16 @@ from jedi.evaluate import compiled
from jedi.evaluate import filters
from jedi.evaluate.context import Context, LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import memoize_default
from jedi.evaluate.param import ValuesArguments
from jedi.cache import memoize_method
from jedi.evaluate import representation as er
from jedi.evaluate.dynamic import search_params
from jedi.evaluate import iterable
class AbstractInstanceContext(Context):
"""
This class is used to evaluate instances.
"""
_faked_class = None
def __init__(self, evaluator, parent_context, class_context, var_args):
super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
# Generated instances are classes that are just generated by self
@@ -27,23 +24,6 @@ class AbstractInstanceContext(Context):
self.class_context = class_context
self.var_args = var_args
#####
""""
if class_context.name.string_name in ['list', 'set'] \
and evaluator.BUILTINS == parent_context.get_root_context():
# compare the module path with the builtin name.
self.var_args = iterable.check_array_instances(evaluator, self)
elif not is_generated:
# Need to execute the __init__ function, because the dynamic param
# searching needs it.
try:
method = self.get_subscope_by_name('__init__')
except KeyError:
pass
else:
self._init_execution = evaluator.execute(method, self.var_args)
"""
def is_class(self):
return False
@@ -191,6 +171,16 @@ class AbstractInstanceContext(Context):
class CompiledInstance(AbstractInstanceContext):
def __init__(self, *args, **kwargs):
super(CompiledInstance, self).__init__(*args, **kwargs)
# I don't think that dynamic append lookups should happen here. That
# sounds more like something that should go to py__iter__.
if self.class_context.name.string_name in ['list', 'set'] \
and self.parent_context.get_root_context() == self.evaluator.BUILTINS:
# compare the module path with the builtin name.
self.var_args = iterable.get_dynamic_array_instance(self)
@property
def name(self):
return compiled.CompiledContextName(self, self.class_context.name.string_name)

View File

@@ -399,9 +399,8 @@ class ArrayLiteralContext(ArrayMixin, AbstractSequence):
for node in self._items():
yield context.LazyTreeContext(self._defining_context, node)
additions = check_array_additions(self.evaluator, self)
if additions:
yield additions
for addition in check_array_additions(self._defining_context, self):
yield addition
def _values(self):
"""Returns a list of a list of node."""
@@ -683,55 +682,46 @@ def py__getitem__(evaluator, context, types, trailer):
return result
def check_array_additions(evaluator, array):
def check_array_additions(context, sequence):
""" Just a mapper function for the internal _check_array_additions """
if array.array_type not in ('list', 'set'):
if sequence.array_type not in ('list', 'set'):
# TODO also check for dict updates
return set()
return set()
is_list = array.array_type == 'list'
try:
current_module = array.atom.get_parent_until()
except AttributeError:
# If there's no get_parent_until, it's a FakeSequence or another Fake
# type. Those fake types are used inside Jedi's engine. No values may
# be added to those after their creation.
return set()
return _check_array_additions(evaluator, array, current_module, is_list)
return _check_array_additions(context, sequence)
@memoize_default(default=set(), evaluator_is_first_arg=True)
@memoize_default(default=set())
@debug.increase_indent
def _check_array_additions(evaluator, compare_array, module, is_list):
def _check_array_additions(context, sequence):
"""
Checks if a `Array` has "add" (append, insert, extend) statements:
>>> a = [""]
>>> a.append(1)
"""
debug.dbg('Dynamic array search for %s' % compare_array, color='MAGENTA')
if not settings.dynamic_array_additions or isinstance(module, compiled.CompiledObject):
from jedi.evaluate import representation as er, param
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
module_context = context.get_root_context()
if not settings.dynamic_array_additions or isinstance(module_context, compiled.CompiledObject):
debug.dbg('Dynamic array search aborted.', color='MAGENTA')
return set()
def check_additions(arglist, add_name):
params = list(param.Arguments(evaluator, context, arglist).unpack())
def find_additions(context, arglist, add_name):
params = list(param.TreeArguments(context.evaluator, context, arglist).unpack())
result = set()
if add_name in ['insert']:
params = params[1:]
if add_name in ['append', 'add', 'insert']:
for key, nodes in params:
result |= unite(evaluator.eval_element(node) for node in nodes)
for key, lazy_context in params:
result.add(lazy_context)
elif add_name in ['extend', 'update']:
for key, nodes in params:
for node in nodes:
types = evaluator.eval_element(node)
result |= py__iter__types(evaluator, types, node)
for key, lazy_context in params:
result |= set(py__iter__(context.evaluator, lazy_context.infer()))
return result
from jedi.evaluate import representation as er, param
'''
def get_execution_parent(element):
""" Used to get an Instance/FunctionExecution parent """
if isinstance(element, Array):
@@ -744,21 +734,27 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
if isinstance(node, er.InstanceElement) or node is None:
return node
return node.get_parent_until(er.FunctionExecution)
'''
temp_param_add, settings.dynamic_params_for_other_modules = \
settings.dynamic_params_for_other_modules, False
search_names = ['append', 'extend', 'insert'] if is_list else ['add', 'update']
comp_arr_parent = get_execution_parent(compare_array)
is_list = sequence.name.string_name == 'list'
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
#comp_arr_parent = None
added_types = set()
for add_name in search_names:
try:
possible_names = module.used_names[add_name]
possible_names = module_context.module_node.used_names[add_name]
except KeyError:
continue
else:
for name in possible_names:
context_node = context.get_node()
if not (context_node.start_pos < name.start_pos < context_node.end_pos):
continue
'''
# Check if the original scope is an execution. If it is, one
# can search for the same statement, that is in the module
# dict. Executions are somewhat special in jedi, since they
@@ -772,6 +768,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
# improves Jedi's speed for array lookups, since we
# don't have to check the whole source tree anymore.
continue
'''
trailer = name.parent
power = trailer.parent
trailer_pos = power.children.index(trailer)
@@ -784,36 +781,42 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
or execution_trailer.children[0] != '(' \
or execution_trailer.children[1] == ')':
continue
power = helpers.call_of_leaf(name, cut_own_trailer=True)
# InstanceElements are special, because they don't get copied,
# but have this wrapper around them.
if isinstance(comp_arr_parent, er.InstanceElement):
power = er.get_instance_el(evaluator, comp_arr_parent.instance, power)
if evaluator.recursion_detector.push_stmt(power):
random_context = context.create_context(name)
if context.evaluator.recursion_detector.push_stmt(power):
# Check for recursion. Possible by using 'extend' in
# combination with function calls.
continue
try:
if compare_array in evaluator.eval_element(power):
found = helpers.evaluate_call_of_leaf(
random_context,
name,
cut_own_trailer=True
)
if sequence in found:
# The arrays match. Now add the results
added_types |= check_additions(execution_trailer.children[1], add_name)
added_types |= find_additions(
random_context,
execution_trailer.children[1],
add_name
)
finally:
evaluator.recursion_detector.pop_stmt()
context.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')
return added_types
def check_array_instances(evaluator, instance):
def get_dynamic_array_instance(instance):
"""Used for set() and list() instances."""
if not settings.dynamic_array_additions:
return instance.var_args
ai = _ArrayInstance(evaluator, instance)
ai = _ArrayInstance(instance)
from jedi.evaluate import param
return param.Arguments(evaluator, instance, [AlreadyEvaluated([ai])])
return param.ValuesArguments([[ai]])
class _ArrayInstance(object):
@@ -827,29 +830,25 @@ class _ArrayInstance(object):
and therefore doesn't need `names_dicts`, `py__bool__` and so on, because
we don't use these operations in `builtins.py`.
"""
def __init__(self, evaluator, instance):
self.evaluator = evaluator
def __init__(self, instance):
self.instance = instance
self.var_args = instance.var_args
def py__iter__(self):
raise NotImplementedError
var_args = self.var_args
try:
_, first_nodes = next(self.var_args.unpack())
_, lazy_context = next(var_args.unpack())
except StopIteration:
types = set()
pass
else:
types = unite(self.evaluator.eval_element(node) for node in first_nodes)
for types in py__iter__(self.evaluator, types, first_nodes[0]):
yield types
for lazy in py__iter__(self.instance.evaluator, lazy_context.infer()):
yield lazy
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:
yield additions
from jedi.evaluate import param
if isinstance(var_args, param.TreeArguments):
additions = _check_array_additions(var_args.context, self.instance)
for addition in additions:
yield addition
class Slice(context.Context):