diff --git a/dynamic.py b/dynamic.py index 60e36f8a..ac34d1b4 100644 --- a/dynamic.py +++ b/dynamic.py @@ -45,6 +45,7 @@ class ParamListener(object): self.param_possibilities.append(params) +@evaluate.memoize_default([]) def search_params(param): """ This is a dynamic search for params. If you try to complete a type: @@ -123,6 +124,18 @@ def check_array_additions(array): res = _check_array_additions(array, current_module, is_list) return res +counter = 0 +def dec(func): + def wrapper(*args, **kwargs): + global counter + a = args[0]._array.parent_stmt() + print ' '*counter + 'recursion,', a, id(a) + counter += 1 + res = func(*args, **kwargs) + counter -= 1 + print ' '*counter + 'end,', args[0] + return res + return wrapper @evaluate.memoize_default([]) def _check_array_additions(compare_array, module, is_list): @@ -170,26 +183,6 @@ def _check_array_additions(compare_array, module, is_list): found = evaluate.follow_call_path(backtrack_path, scope, position) settings.evaluate_special_assignments = True if not compare_array in found: - # 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, evaluate.Execution): - found_bases = [] - for f in found: - base = get_execution_parent(f, parsing.Function) - found_bases.append(base) - if comp_arr_parent.base.base_func in found_bases: - stmt = comp_arr_parent. \ - get_statement_for_position(c.start_pos) - if stmt is not None: - if evaluate.follow_statement.push_stmt(stmt): - # check recursion - continue - ass = stmt.get_assignment_calls() - new_calls = scan_array(ass, add_name) - result += check_calls(new_calls, add_name) - evaluate.follow_statement.pop_stmt() continue params = call_path[separate_index + 1] @@ -215,24 +208,41 @@ def _check_array_additions(compare_array, module, is_list): else: # must be instance stmt = element.var_args.parent_stmt() + if isinstance(stmt, evaluate.InstanceElement): + stop_classes = list(stop_classes) + [parsing.Function] return stmt.get_parent_until(*stop_classes) search_names = ['append', 'extend', 'insert'] if is_list else \ ['add', 'update'] comp_arr_parent = get_execution_parent(compare_array, evaluate.Execution) possible_stmts = [] - result = [] + res = [] for n in search_names: try: possible_stmts += module.used_names[n] except KeyError: continue for stmt in possible_stmts: - ass = stmt.get_assignment_calls() - result += check_calls(scan_array(ass, n), n) - - return result + # 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, evaluate.Execution): + 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, evaluate.InstanceElement): + stmt = evaluate.InstanceElement(comp_arr_parent.instance, stmt) + if evaluate.follow_statement.push_stmt(stmt): + # check recursion + continue + res += check_calls(scan_array(stmt.get_assignment_calls(), n), n) + evaluate.follow_statement.pop_stmt() + return res def check_array_instances(instance): if not settings.dynamic_arrays_instances: diff --git a/evaluate.py b/evaluate.py index 1625b1dc..062d919e 100644 --- a/evaluate.py +++ b/evaluate.py @@ -139,6 +139,8 @@ class Executable(parsing.Base): class Instance(Executable): """ This class is used to evaluate instances. """ + __metaclass__ = CachedMetaClass + def __init__(self, base, var_args=parsing.Array(None, None)): super(Instance, self).__init__(base, var_args) if str(base.name) in ['list', 'set'] \ diff --git a/parsing.py b/parsing.py index 61072120..1ae5befb 100644 --- a/parsing.py +++ b/parsing.py @@ -80,7 +80,7 @@ class Simple(Base): def get_parent_until(self, *classes): """ Takes always the parent, until one class (not a Class) """ scope = self - while not (scope.parent() is None or scope.__class__ in classes): + while not (scope.parent() is None or scope.isinstance(*classes)): scope = scope.parent() return scope diff --git a/test/completion/arrays.py b/test/completion/arrays.py index b74895df..f0858d2e 100644 --- a/test/completion/arrays.py +++ b/test/completion/arrays.py @@ -231,5 +231,5 @@ tuple({1})[0] tuple((1,))[0] # implementation detail for lists, should not be visible -#? [] +##? [] list().__iterable diff --git a/test/completion/classes.py b/test/completion/classes.py index 71ac7030..4b989bb8 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -404,3 +404,17 @@ E.t(1) e.u(1) #? str() E.u(1) + +# ----------------- +# recursions +# ----------------- +def Recursion(): + def recurse(self): + self.a = self.a + self.b = self.b.recurse() + +#? +Recursion().a + +#? +Recursion().b diff --git a/test/completion/dynamic.py b/test/completion/dynamic.py index b33ef307..ec5f766b 100644 --- a/test/completion/dynamic.py +++ b/test/completion/dynamic.py @@ -316,12 +316,23 @@ class C(): a[0] return a + def class_arr(self, el): + self.a = [] + self.a.append(el) + #? int() + self.a[0] + return self.a + #? int() C().blub(1)[0] - #? float() C().blub2(1)[0] +#? int() +C().a[0] +#? int() +C().class_arr(1)[0] + # ----------------- # array recursions # ----------------- diff --git a/test/run.py b/test/run.py index b753cc94..7303d8d3 100755 --- a/test/run.py +++ b/test/run.py @@ -198,6 +198,8 @@ def test_dir(completion_test_dir, thirdparty=False): summary.append(s) +# Sorry I didn't use argparse here. It's because argparse is not in the +# stdlib in 2.5. args = sys.argv[1:] try: i = args.index('--thirdparty')