diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 09b64e1c..af209ed9 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -547,7 +547,7 @@ class Evaluator(object): result.append(er.Function(self, call)) # With things like params, these can also be functions... elif isinstance(call, pr.Base) and call.isinstance( - er.Function, er.Class, er.Instance, dynamic.ArrayInstance): + er.Function, er.Class, er.Instance, iterable.ArrayInstance): result.append(call) # The string tokens are just operations (+, -, etc.) elif not isinstance(call, (str, unicode)): @@ -764,7 +764,7 @@ def get_iterator_types(inputs): # Take the first statement (for has always only # one, remember `in`). And follow it. for it in inputs: - if isinstance(it, (iterable.Generator, iterable.Array, dynamic.ArrayInstance)): + if isinstance(it, (iterable.Generator, iterable.Array, iterable.ArrayInstance)): iterators.append(it) else: if not hasattr(it, 'execute_subscope_by_name'): diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index 589ffcd0..16a80c9e 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -57,7 +57,6 @@ from jedi import cache from jedi.parser import representation as pr from jedi import modules from jedi import settings -from jedi import debug from jedi.parser import fast as fast_parser from jedi.evaluate.cache import memoize_default @@ -232,18 +231,6 @@ def search_params(evaluator, param): return result -def check_array_additions(evaluator, array): - """ Just a mapper function for the internal _check_array_additions """ - if not pr.Array.is_type(array._array, pr.Array.LIST, pr.Array.SET): - # TODO also check for dict updates - return [] - - is_list = array._array.type == 'list' - current_module = array._array.get_parent_until() - res = _check_array_additions(evaluator, array, current_module, is_list) - return res - - def _scan_statement(stmt, search_name, assignment_details=False): """ Returns the function Call that match search_name in an Array. """ def scan_array(arr, search_name): @@ -280,163 +267,6 @@ def _scan_statement(stmt, search_name, assignment_details=False): return result -@memoize_default([], evaluator_is_first_arg=True) -def _check_array_additions(evaluator, compare_array, module, is_list): - """ - Checks if a `pr.Array` has "add" statements: - >>> a = [""] - >>> a.append(1) - """ - if not settings.dynamic_array_additions or module.is_builtin(): - return [] - - def check_calls(calls, add_name): - """ - Calls are processed here. The part before the call is searched and - compared with the original Array. - """ - result = [] - for c in calls: - call_path = list(c.generate_call_path()) - separate_index = call_path.index(add_name) - if add_name == call_path[-1] or separate_index == 0: - # this means that there is no execution -> [].append - # or the keyword is at the start -> append() - continue - backtrack_path = iter(call_path[:separate_index]) - - position = c.start_pos - scope = c.get_parent_until(pr.IsScope) - - found = evaluator.eval_call_path(backtrack_path, scope, position) - if not compare_array in found: - continue - - params = call_path[separate_index + 1] - if not params.values: - continue # no params: just ignore it - if add_name in ['append', 'add']: - for param in params: - result += evaluator.eval_statement(param) - elif add_name in ['insert']: - try: - second_param = params[1] - except IndexError: - continue - else: - result += evaluator.eval_statement(second_param) - elif add_name in ['extend', 'update']: - for param in params: - iterators = evaluator.eval_statement(param) - result += evaluate.get_iterator_types(iterators) - return result - - from jedi.evaluate import representation as er - from jedi import evaluate - from jedi.evaluate import iterable - - def get_execution_parent(element, *stop_classes): - """ Used to get an Instance/FunctionExecution parent """ - if isinstance(element, iterable.Array): - stmt = element._array.parent - else: - # is an Instance with an ArrayInstance inside - stmt = element.var_args[0].var_args.parent - if isinstance(stmt, er.InstanceElement): - stop_classes = list(stop_classes) + [er.Function] - return stmt.get_parent_until(stop_classes) - - 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, er.FunctionExecution) - - possible_stmts = [] - res = [] - for n in search_names: - try: - possible_stmts += module.used_names[n] - except KeyError: - continue - for stmt in possible_stmts: - # 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 - # literally copy the contents of a function. - if isinstance(comp_arr_parent, er.FunctionExecution): - stmt = comp_arr_parent. \ - get_statement_for_position(stmt.start_pos) - if stmt is None: - continue - # InstanceElements are special, because they don't get copied, - # but have this wrapper around them. - if isinstance(comp_arr_parent, er.InstanceElement): - stmt = er.InstanceElement(comp_arr_parent.instance, stmt) - - if evaluator.recursion_detector.push_stmt(stmt): - # check recursion - continue - res += check_calls(_scan_statement(stmt, n), n) - evaluator.recursion_detector.pop_stmt() - # reset settings - settings.dynamic_params_for_other_modules = temp_param_add - return res - - -def check_array_instances(evaluator, instance): - """Used for set() and list() instances.""" - if not settings.dynamic_arrays_instances: - return instance.var_args - ai = ArrayInstance(evaluator, instance) - return [ai] - - -class ArrayInstance(pr.Base): - """ - Used for the usage of set() and list(). - This is definitely a hack, but a good one :-) - It makes it possible to use set/list conversions. - """ - def __init__(self, evaluator, instance): - self._evaluator = evaluator - self.instance = instance - self.var_args = instance.var_args - - def iter_content(self): - """ - The index is here just ignored, because of all the appends, etc. - lists/sets are too complicated too handle that. - """ - items = [] - from jedi import evaluate - for stmt in self.var_args: - for typ in self._evaluator.eval_statement(stmt): - if isinstance(typ, evaluate.er.Instance) and len(typ.var_args): - array = typ.var_args[0] - if isinstance(array, ArrayInstance): - # prevent recursions - # TODO compare Modules - if self.var_args.start_pos != array.var_args.start_pos: - items += array.iter_content() - else: - debug.warning( - 'ArrayInstance recursion', - self.var_args) - continue - items += evaluate.get_iterator_types([typ]) - - # TODO check if exclusion of tuple is a problem here. - if isinstance(self.var_args, tuple) or self.var_args.parent is None: - return [] # generated var_args should not be checked for arrays - - module = self.var_args.get_parent_until() - is_list = str(self.instance.name) == 'list' - items += _check_array_additions(self._evaluator, self.instance, module, is_list) - return items - - def check_flow_information(evaluator, flow, search_name, pos): """ Try to find out the type of a variable just with the information that is given by the flows: e.g. It is also responsible for assert checks.:: diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index cb655760..c79695a0 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -1,12 +1,13 @@ import itertools -from jedi._compatibility import use_metaclass from jedi import common -from jedi.parser import representation as pr from jedi import debug +from jedi import settings +from jedi._compatibility import use_metaclass +from jedi.parser import representation as pr from jedi.evaluate import builtin from jedi.evaluate import dynamic -from jedi.evaluate.cache import CachedMetaClass +from jedi.evaluate.cache import CachedMetaClass, memoize_default class Generator(use_metaclass(CachedMetaClass, pr.Base)): @@ -89,7 +90,7 @@ class Array(use_metaclass(CachedMetaClass, pr.Base)): return self.get_exact_index_types(index.var_args[0]) result = list(self._follow_values(self._array.values)) - result += dynamic.check_array_additions(self._evaluator, self) + result += check_array_additions(self._evaluator, self) return set(result) def get_exact_index_types(self, mixed_index): @@ -180,3 +181,172 @@ class ArrayMethod(object): def __repr__(self): return "<%s of %s>" % (type(self).__name__, self.name) + + +def check_array_additions(evaluator, array): + """ Just a mapper function for the internal _check_array_additions """ + if not pr.Array.is_type(array._array, pr.Array.LIST, pr.Array.SET): + # TODO also check for dict updates + return [] + + is_list = array._array.type == 'list' + current_module = array._array.get_parent_until() + res = _check_array_additions(evaluator, array, current_module, is_list) + return res + + +@memoize_default([], evaluator_is_first_arg=True) +def _check_array_additions(evaluator, compare_array, module, is_list): + """ + Checks if a `pr.Array` has "add" statements: + >>> a = [""] + >>> a.append(1) + """ + if not settings.dynamic_array_additions or module.is_builtin(): + return [] + + def check_calls(calls, add_name): + """ + Calls are processed here. The part before the call is searched and + compared with the original Array. + """ + result = [] + for c in calls: + call_path = list(c.generate_call_path()) + separate_index = call_path.index(add_name) + if add_name == call_path[-1] or separate_index == 0: + # this means that there is no execution -> [].append + # or the keyword is at the start -> append() + continue + backtrack_path = iter(call_path[:separate_index]) + + position = c.start_pos + scope = c.get_parent_until(pr.IsScope) + + found = evaluator.eval_call_path(backtrack_path, scope, position) + if not compare_array in found: + continue + + params = call_path[separate_index + 1] + if not params.values: + continue # no params: just ignore it + if add_name in ['append', 'add']: + for param in params: + result += evaluator.eval_statement(param) + elif add_name in ['insert']: + try: + second_param = params[1] + except IndexError: + continue + else: + result += evaluator.eval_statement(second_param) + elif add_name in ['extend', 'update']: + for param in params: + iterators = evaluator.eval_statement(param) + result += evaluate.get_iterator_types(iterators) + return result + + from jedi.evaluate import representation as er + from jedi import evaluate + from jedi.evaluate import iterable + + def get_execution_parent(element, *stop_classes): + """ Used to get an Instance/FunctionExecution parent """ + if isinstance(element, iterable.Array): + stmt = element._array.parent + else: + # is an Instance with an ArrayInstance inside + stmt = element.var_args[0].var_args.parent + if isinstance(stmt, er.InstanceElement): + stop_classes = list(stop_classes) + [er.Function] + return stmt.get_parent_until(stop_classes) + + 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, er.FunctionExecution) + + possible_stmts = [] + res = [] + for n in search_names: + try: + possible_stmts += module.used_names[n] + except KeyError: + continue + for stmt in possible_stmts: + # 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 + # literally copy the contents of a function. + if isinstance(comp_arr_parent, er.FunctionExecution): + stmt = comp_arr_parent. \ + get_statement_for_position(stmt.start_pos) + if stmt is None: + continue + # InstanceElements are special, because they don't get copied, + # but have this wrapper around them. + if isinstance(comp_arr_parent, er.InstanceElement): + stmt = er.InstanceElement(comp_arr_parent.instance, stmt) + + if evaluator.recursion_detector.push_stmt(stmt): + # check recursion + continue + res += check_calls(dynamic._scan_statement(stmt, n), n) + evaluator.recursion_detector.pop_stmt() + # reset settings + settings.dynamic_params_for_other_modules = temp_param_add + return res + + +def check_array_instances(evaluator, instance): + """Used for set() and list() instances.""" + if not settings.dynamic_arrays_instances: + return instance.var_args + ai = ArrayInstance(evaluator, instance) + return [ai] + + +class ArrayInstance(pr.Base): + """ + Used for the usage of set() and list(). + This is definitely a hack, but a good one :-) + It makes it possible to use set/list conversions. + """ + def __init__(self, evaluator, instance): + self._evaluator = evaluator + self.instance = instance + self.var_args = instance.var_args + + def iter_content(self): + """ + The index is here just ignored, because of all the appends, etc. + lists/sets are too complicated too handle that. + """ + items = [] + from jedi import evaluate + for stmt in self.var_args: + for typ in self._evaluator.eval_statement(stmt): + if isinstance(typ, evaluate.er.Instance) and len(typ.var_args): + array = typ.var_args[0] + if isinstance(array, ArrayInstance): + # prevent recursions + # TODO compare Modules + if self.var_args.start_pos != array.var_args.start_pos: + items += array.iter_content() + else: + debug.warning( + 'ArrayInstance recursion', + self.var_args) + continue + items += evaluate.get_iterator_types([typ]) + + # TODO check if exclusion of tuple is a problem here. + if isinstance(self.var_args, tuple) or self.var_args.parent is None: + return [] # generated var_args should not be checked for arrays + + module = self.var_args.get_parent_until() + is_list = str(self.instance.name) == 'list' + items += _check_array_additions(self._evaluator, self.instance, module, is_list) + return items diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 680e9495..3b09ff67 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -19,9 +19,8 @@ from jedi import common from jedi.evaluate import builtin from jedi.evaluate import recursion from jedi.evaluate.cache import memoize_default, CachedMetaClass -from jedi.evaluate.iterable import Array, Generator +from jedi.evaluate import iterable from jedi import docstrings -from jedi.evaluate import dynamic class Executable(pr.IsScope): @@ -51,7 +50,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): if str(base.name) in ['list', 'set'] \ and builtin.Builtin.scope == base.get_parent_until(): # compare the module path with the builtin name. - self.var_args = dynamic.check_array_instances(evaluator, self) + self.var_args = iterable.check_array_instances(evaluator, self) else: # need to execute the __init__ function, because the dynamic param # searching needs it. @@ -406,7 +405,7 @@ class FunctionExecution(Executable): for listener in func.listeners: listener.execute(self._get_params()) if func.is_generator and not evaluate_generator: - return [Generator(self._evaluator, func, self.var_args)] + return [iterable.Generator(self._evaluator, func, self.var_args)] else: stmts = docstrings.find_return_types(self._evaluator, func) for r in self.returns: @@ -570,17 +569,17 @@ class FunctionExecution(Executable): # *args must be some sort of an array, otherwise -> ignore for array in arrays: - if isinstance(array, Array): + if isinstance(array, iterable.Array): for field_stmt in array: # yield from plz! yield None, field_stmt - elif isinstance(array, Generator): + elif isinstance(array, iterable.Generator): for field_stmt in array.iter_content(): yield None, helpers.FakeStatement(field_stmt) # **kwargs elif expression_list[0] == '**': arrays = self._evaluator.eval_expression_list(expression_list[1:]) for array in arrays: - if isinstance(array, Array): + if isinstance(array, iterable.Array): for key_stmt, value_stmt in array.items(): # first index, is the key if syntactically correct call = key_stmt.expression_list()[0]