diff --git a/jedi/evaluate/context.py b/jedi/evaluate/context.py index f00e8bba..2f68a195 100644 --- a/jedi/evaluate/context.py +++ b/jedi/evaluate/context.py @@ -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): diff --git a/jedi/evaluate/instance.py b/jedi/evaluate/instance.py index e4fa5f33..c0258c5d 100644 --- a/jedi/evaluate/instance.py +++ b/jedi/evaluate/instance.py @@ -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) diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 9bd14f32..801f8d71 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -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):