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): search_global=False, is_goto=False):
return self.evaluator.find_types(self, name_or_str, position, search_global, is_goto) 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): class TreeContext(Context):
def __init__(self, evaluator, parent_context=None): 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 import filters
from jedi.evaluate.context import Context, LazyKnownContext, LazyKnownContexts from jedi.evaluate.context import Context, LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import memoize_default from jedi.evaluate.cache import memoize_default
from jedi.evaluate.param import ValuesArguments
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate.dynamic import search_params from jedi.evaluate.dynamic import search_params
from jedi.evaluate import iterable
class AbstractInstanceContext(Context): class AbstractInstanceContext(Context):
""" """
This class is used to evaluate instances. This class is used to evaluate instances.
""" """
_faked_class = None
def __init__(self, evaluator, parent_context, class_context, var_args): def __init__(self, evaluator, parent_context, class_context, var_args):
super(AbstractInstanceContext, self).__init__(evaluator, parent_context) super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
# Generated instances are classes that are just generated by self # Generated instances are classes that are just generated by self
@@ -27,23 +24,6 @@ class AbstractInstanceContext(Context):
self.class_context = class_context self.class_context = class_context
self.var_args = var_args 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): def is_class(self):
return False return False
@@ -191,6 +171,16 @@ class AbstractInstanceContext(Context):
class CompiledInstance(AbstractInstanceContext): 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 @property
def name(self): def name(self):
return compiled.CompiledContextName(self, self.class_context.name.string_name) 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(): for node in self._items():
yield context.LazyTreeContext(self._defining_context, node) yield context.LazyTreeContext(self._defining_context, node)
additions = check_array_additions(self.evaluator, self) for addition in check_array_additions(self._defining_context, self):
if additions: yield addition
yield additions
def _values(self): def _values(self):
"""Returns a list of a list of node.""" """Returns a list of a list of node."""
@@ -683,55 +682,46 @@ def py__getitem__(evaluator, context, types, trailer):
return result return result
def check_array_additions(evaluator, array): def check_array_additions(context, sequence):
""" Just a mapper function for the internal _check_array_additions """ """ 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 # TODO also check for dict updates
return set() return set()
return set() return _check_array_additions(context, sequence)
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)
@memoize_default(default=set(), evaluator_is_first_arg=True) @memoize_default(default=set())
@debug.increase_indent @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: Checks if a `Array` has "add" (append, insert, extend) statements:
>>> a = [""] >>> a = [""]
>>> a.append(1) >>> a.append(1)
""" """
debug.dbg('Dynamic array search for %s' % compare_array, color='MAGENTA') from jedi.evaluate import representation as er, param
if not settings.dynamic_array_additions or isinstance(module, compiled.CompiledObject):
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') debug.dbg('Dynamic array search aborted.', color='MAGENTA')
return set() return set()
def check_additions(arglist, add_name): def find_additions(context, arglist, add_name):
params = list(param.Arguments(evaluator, context, arglist).unpack()) params = list(param.TreeArguments(context.evaluator, context, arglist).unpack())
result = set() result = set()
if add_name in ['insert']: if add_name in ['insert']:
params = params[1:] params = params[1:]
if add_name in ['append', 'add', 'insert']: if add_name in ['append', 'add', 'insert']:
for key, nodes in params: for key, lazy_context in params:
result |= unite(evaluator.eval_element(node) for node in nodes) result.add(lazy_context)
elif add_name in ['extend', 'update']: elif add_name in ['extend', 'update']:
for key, nodes in params: for key, lazy_context in params:
for node in nodes: result |= set(py__iter__(context.evaluator, lazy_context.infer()))
types = evaluator.eval_element(node)
result |= py__iter__types(evaluator, types, node)
return result return result
from jedi.evaluate import representation as er, param '''
def get_execution_parent(element): def get_execution_parent(element):
""" Used to get an Instance/FunctionExecution parent """ """ Used to get an Instance/FunctionExecution parent """
if isinstance(element, Array): 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: if isinstance(node, er.InstanceElement) or node is None:
return node return node
return node.get_parent_until(er.FunctionExecution) return node.get_parent_until(er.FunctionExecution)
'''
temp_param_add, settings.dynamic_params_for_other_modules = \ temp_param_add, settings.dynamic_params_for_other_modules = \
settings.dynamic_params_for_other_modules, False settings.dynamic_params_for_other_modules, False
search_names = ['append', 'extend', 'insert'] if is_list else ['add', 'update'] is_list = sequence.name.string_name == 'list'
comp_arr_parent = get_execution_parent(compare_array) search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
#comp_arr_parent = None
added_types = set() added_types = set()
for add_name in search_names: for add_name in search_names:
try: try:
possible_names = module.used_names[add_name] possible_names = module_context.module_node.used_names[add_name]
except KeyError: except KeyError:
continue continue
else: else:
for name in possible_names: 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 # Check if the original scope is an execution. If it is, one
# can search for the same statement, that is in the module # can search for the same statement, that is in the module
# dict. Executions are somewhat special in jedi, since they # 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 # improves Jedi's speed for array lookups, since we
# don't have to check the whole source tree anymore. # don't have to check the whole source tree anymore.
continue continue
'''
trailer = name.parent trailer = name.parent
power = trailer.parent power = trailer.parent
trailer_pos = power.children.index(trailer) 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[0] != '(' \
or execution_trailer.children[1] == ')': or execution_trailer.children[1] == ')':
continue 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 # Check for recursion. Possible by using 'extend' in
# combination with function calls. # combination with function calls.
continue continue
try: 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 # 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: finally:
evaluator.recursion_detector.pop_stmt() context.evaluator.recursion_detector.pop_stmt()
# reset settings # reset settings
settings.dynamic_params_for_other_modules = temp_param_add settings.dynamic_params_for_other_modules = temp_param_add
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA') debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
return added_types return added_types
def check_array_instances(evaluator, instance): def get_dynamic_array_instance(instance):
"""Used for set() and list() instances.""" """Used for set() and list() instances."""
if not settings.dynamic_array_additions: if not settings.dynamic_array_additions:
return instance.var_args return instance.var_args
ai = _ArrayInstance(evaluator, instance) ai = _ArrayInstance(instance)
from jedi.evaluate import param from jedi.evaluate import param
return param.Arguments(evaluator, instance, [AlreadyEvaluated([ai])]) return param.ValuesArguments([[ai]])
class _ArrayInstance(object): class _ArrayInstance(object):
@@ -827,29 +830,25 @@ class _ArrayInstance(object):
and therefore doesn't need `names_dicts`, `py__bool__` and so on, because and therefore doesn't need `names_dicts`, `py__bool__` and so on, because
we don't use these operations in `builtins.py`. we don't use these operations in `builtins.py`.
""" """
def __init__(self, evaluator, instance): def __init__(self, instance):
self.evaluator = evaluator
self.instance = instance self.instance = instance
self.var_args = instance.var_args self.var_args = instance.var_args
def py__iter__(self): def py__iter__(self):
raise NotImplementedError var_args = self.var_args
try: try:
_, first_nodes = next(self.var_args.unpack()) _, lazy_context = next(var_args.unpack())
except StopIteration: except StopIteration:
types = set() pass
else: else:
types = unite(self.evaluator.eval_element(node) for node in first_nodes) for lazy in py__iter__(self.instance.evaluator, lazy_context.infer()):
for types in py__iter__(self.evaluator, types, first_nodes[0]): yield lazy
yield types
module = self.var_args.get_parent_until() from jedi.evaluate import param
if module is None: if isinstance(var_args, param.TreeArguments):
return additions = _check_array_additions(var_args.context, self.instance)
is_list = str(self.instance.name) == 'list' for addition in additions:
additions = _check_array_additions(self.evaluator, self.instance, module, is_list) yield addition
if additions:
yield additions
class Slice(context.Context): class Slice(context.Context):