diff --git a/evaluate.py b/evaluate.py index 4a0218b6..a42326f7 100644 --- a/evaluate.py +++ b/evaluate.py @@ -15,6 +15,7 @@ TODO nonlocal statement TODO getattr / __getattr__ / __getattribute__ ? TODO descriptors +TODO @staticmethod @classmethod """ from _compatibility import next, property @@ -157,7 +158,7 @@ class Instance(Executable): class_names = self.base.get_defined_names() for var in class_names: # functions are also instance elements - if isinstance(var.parent, (parsing.Function, Function)): + if isinstance(var.parent, (Function, parsing.Function)): var = InstanceElement(self, var) names.append(var) @@ -187,8 +188,12 @@ class InstanceElement(object): self.var = var @property + @memoize_default() def parent(self): - return InstanceElement(self.instance, self.var.parent) + par = self.var.parent + if isinstance(par, parsing.Function): + par = Function(par) + return InstanceElement(self.instance, par) def get_parent_until(self, *classes): scope = self.var.get_parent_until(*classes) @@ -261,19 +266,21 @@ class Function(object): def __init__(self, func, is_decorated=False): """ This should not be called directly """ self.base_func = func - self.func = self.process_decorators(is_decorated) + self.is_decorated = is_decorated - def __getattr__(self, name): - return getattr(self.func, name) - - def process_decorators(self, is_decorated): - """ Returns the function, that is to be executed in the end """ - func = self.base_func + @property + @memoize_default() + def func(self): + """ + Returns the function, that is to be executed in the end. + This is also the places where the decorators are processed. + """ + f = self.base_func # only enter it, if has not already been processed - if not is_decorated: + if not self.is_decorated: for dec in reversed(self.base_func.decorators): - debug.dbg('decorator:', dec, func) + debug.dbg('decorator:', dec, f) dec_results = follow_statement(dec) if not len(dec_results): debug.warning('decorator func not found', self.base_func) @@ -283,7 +290,7 @@ class Function(object): dec_results) decorator = dec_results.pop() # create param array - old_func = Function(func, is_decorated=True) + old_func = Function(f, is_decorated=True) params = parsing.Array(parsing.Array.NOARRAY, old_func) params.values = [[old_func]] wrappers = Execution(decorator, params).get_return_types() @@ -294,16 +301,18 @@ class Function(object): debug.warning('multiple wrappers found', self.base_func, wrappers) # this is here, that the wrapper gets executed - func = wrappers[0] + f = wrappers[0] debug.dbg('decorator end') - if func != self.base_func: - return Function(func) - else: - return func + if f != self.base_func and isinstance(f, parsing.Function): + f = Function(f) + return f + + def __getattr__(self, name): + return getattr(self.func, name) def __repr__(self): - return "" % (self.__class__.__name__, self.func) + return "" % (self.__class__.__name__, self.base_func) class Execution(Executable): @@ -313,8 +322,6 @@ class Execution(Executable): This is the most complicated class, because it contains the logic to transfer parameters. This is even more complicated, because there may be multiple call to functions and recursion has to be avoided. - - TODO InstantElements ? """ cache = {} @@ -342,10 +349,15 @@ class Execution(Executable): except (AttributeError, KeyError): debug.warning("no execution possible", self.base) else: + debug.dbg('__call__', call_method, self.base) + base = self.base + if isinstance(self.base, Function): + base = self.base.func + call_method = InstanceElement(base, call_method) exe = Execution(call_method, self.var_args) stmts = exe.get_return_types() - debug.dbg('exec results:', stmts, self.base, repr(self)) + debug.dbg('exec result: %s in %s' % (stmts, self)) return strip_imports(stmts) @@ -844,7 +856,7 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False): # if there are results, ignore the other scopes if result: break - debug.dbg('sfn filter', name_str, result) + debug.dbg('sfn filter "%s" in %s: %s' % (name_str, scope, result)) return result if search_global: @@ -932,10 +944,9 @@ def follow_statement(stmt, scope=None, seek_name=None): scope = stmt.get_parent_until(parsing.Function, Function, Execution, parsing.Class, Class, Instance, InstanceElement) - debug.dbg('follow_stmt', stmt, stmt.parent, 'in', scope, seek_name) - + debug.dbg('follow_stmt %s in %s (%s)' % (stmt, scope, seek_name)) call_list = stmt.get_assignment_calls() - debug.dbg('calls', call_list, call_list.values) + debug.dbg('calls: %s' % call_list) result = follow_call_list(scope, call_list) # assignment checking is only important if the statement defines multiple @@ -1039,7 +1050,7 @@ def follow_path(path, scope, position=None): current = next(path) except StopIteration: return None - debug.dbg('follow', current, scope) + debug.dbg('follow %s in scope %s' % (current, scope)) result = [] if isinstance(current, parsing.Array): @@ -1048,7 +1059,7 @@ def follow_path(path, scope, position=None): result = scope.get_index_types(current) elif current.type not in [parsing.Array.DICT]: # scope must be a class or func - make an instance or execution - debug.dbg('exec', scope) + debug.dbg('exe', scope) result = Execution(scope, current).get_return_types() else: # curly braces are not allowed, because they make no sense diff --git a/test/completion/classes.py b/test/completion/classes.py index c8acf250..f9ec5ead 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -183,9 +183,27 @@ class B(): def r(self): return 1 - @r.setter - def r(self, value): - pass + #@r.setter + #def r(self, value): + # pass +#? [] +B().r. #? [] B().r() + +class Decorator(object): + def __init__(self, func): + self.func = func + + def __call__(self, *args, **kwargs): + return self.func(1, *args, **kwargs) + +@Decorator +def nothing(a,b,c): + return a,b,c + +#? int() +nothing("")[0] +#? str() +nothing("")[1] diff --git a/test/run.py b/test/run.py index caf80e64..6d86d8ee 100755 --- a/test/run.py +++ b/test/run.py @@ -54,12 +54,15 @@ def run_definition_test(correct, source, line_nr, line): else: should_be = set() for index in re.finditer('(?: +|$)', correct): + if correct == ' ': + continue # -1 for the comment, +3 because of the comment start `#? ` start = index.start() + 3 try: should_be |= defs(line_nr-1, start) except Exception: - print 'could not resolve %s indent %' % (line_nr - 1, start) + print 'could not resolve %s indent %s' % (line_nr - 1, start) + return 1 # because the objects have different ids, `repr` it, then compare it. should_str = sorted(str(r) for r in should_be) is_str = sorted(str(r) for r in result)