diff --git a/dynamic.py b/dynamic.py new file mode 100644 index 00000000..c42ec298 --- /dev/null +++ b/dynamic.py @@ -0,0 +1,68 @@ +""" +For dynamic completion. +""" +import parsing +import evaluate + +# This is something like the sys.path, but only for searching params. It means +# that this is the order in which Jedi searches params. +search_param_modules = ['.'] + +def search_params(param): + def scan_array(arr): + """ Returns the function Calls that match func_name """ + result = [] + for sub in arr: + for s in sub: + if isinstance(s, parsing.Array): + result += scan_array(s) + elif isinstance(s, parsing.Call): + if str(s.name) == func_name: + result.append(s) + return result + + def get_params_for_module(module): + result = [] + try: + possible_stmts = current_module.used_names[func_name] + except KeyError: + return [] + + calls = [] + for stmt in possible_stmts: + calls += scan_array(stmt.get_assignment_calls()) + + for c in calls: + if not c.execution: + continue + + # now check if the call is actually the same method + c.execution, temp = None, c.execution + possible_executions = evaluate.follow_call(c) + is_same_method = False + for e in possible_executions: + is_same_method = e == func \ + or isinstance(e, evaluate.Function) and e.base_func == func + if not is_same_method: + continue + c.execution = temp + + try: + p = c.execution[param_nr] + except IndexError: + pass + else: + result += evaluate.follow_call_list([p]) + return result + + func = param.get_parent_until(parsing.Function) + func_name = str(func.name) + + current_module = param.get_parent_until() + for i, p in enumerate(func.params): + param_nr = i + + result = get_params_for_module(current_module) + + # TODO check other modules + return result diff --git a/evaluate.py b/evaluate.py index 6a48a9d3..0273b2b9 100644 --- a/evaluate.py +++ b/evaluate.py @@ -31,6 +31,7 @@ import debug import builtin import imports import helpers +import dynamic memoize_caches = [] statement_path = [] @@ -476,6 +477,7 @@ class Execution(Executable): new_param.parent = parent_stmt new_param._assignment_calls_calculated = True new_param._assignment_calls = calls + new_param.is_generated = True name = copy.copy(param.get_name()) name.parent = new_param return name @@ -887,6 +889,14 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False): res_new += get_scopes_for_name(r.parent, str(token_name)) else: + # generated objects are used within executions, where + if isinstance(r, parsing.Param) and not r.is_generated: + res_new += dynamic.search_params(r) + if not r.assignment_details: + # this means that there are no default params, so + # just ignore it. + continue + scopes = follow_statement(r, seek_name=name_str) res_new += remove_statements(scopes) else: diff --git a/functions.py b/functions.py index 24641d14..d4fa36ba 100644 --- a/functions.py +++ b/functions.py @@ -5,6 +5,7 @@ import evaluate import modules import debug import imports +# TODO use os.path.sep and similar things import os diff --git a/parsing.py b/parsing.py index 3dffdd60..d67b12ed 100644 --- a/parsing.py +++ b/parsing.py @@ -712,6 +712,7 @@ class Statement(Simple): raise ParserError("Brackets don't match: %s. This is not normal " "behaviour. Please submit a bug" % level) + self._assignment_calls_calculated = True self._assignment_calls = top return top @@ -729,6 +730,7 @@ class Param(Statement): # this is defined by the parser later on, not at the initialization # it is the position in the call (first argument, second...) self.position = None + self.is_generated = False def get_name(self): """ get the name of the param """ @@ -929,16 +931,6 @@ class Name(Simple): def __str__(self): return self.get_code() - def __eq__(self, other): - return self.names == other.names and self.start_pos == other.end_pos - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash(self.names) + hash(self.start_pos[0]) \ - + hash(self.start_pos[1]) - def __len__(self): return len(self.names) @@ -1276,7 +1268,10 @@ class PyFuzzyParser(object): self._check_user_stmt(stmt) if not isinstance(stmt, Param): for tok_name in self.module.temp_used_names: - self.module.used_names[tok_name] = stmt + try: + self.module.used_names[tok_name].append(stmt) + except KeyError: + self.module.used_names[tok_name] = [stmt] self.module.temp_used_names = [] if is_return: # add returns to the scope diff --git a/test/completion/dynamic.py b/test/completion/dynamic.py new file mode 100644 index 00000000..3ea26a08 --- /dev/null +++ b/test/completion/dynamic.py @@ -0,0 +1,14 @@ +""" +This is used for dynamic object completion. +Jedi tries to guess the types with a backtracking approach. +""" +def func(a): + #? int() str() + return a + +#? int() +func(1) + +func + +int(1) + (int(2))+ func('') diff --git a/test/completion/ordering.py b/test/completion/ordering.py index 4534d521..04a6b650 100644 --- a/test/completion/ordering.py +++ b/test/completion/ordering.py @@ -70,6 +70,12 @@ def a(a=3): a(2) #? [] a(2).func + +a_param = 3 +def func(a_param): + # should not be int + #? [] + a_param. # ----------------- # class # -----------------