diff --git a/evaluate.py b/evaluate.py index 506673ac..483bd04e 100644 --- a/evaluate.py +++ b/evaluate.py @@ -7,7 +7,8 @@ follow_statement -> follow_call -> follow_paths -> follow_path TODO nonlocal statement TODO doc TODO list comprehensions, priority? -TODO annotations ? how ? +TODO annotations ? how ? type evaluation and return? +TODO evaluate asserts (type safety) TODO generators """ from _compatibility import next @@ -184,7 +185,12 @@ class Executable(object): arrays = follow_call_list(self.scope, [var_arg[1:]]) for array in arrays: for key, field in array.get_contents(): - yield key[0].name, field + # take the first index + if isinstance(key, parsing.Name): + name = key + else: + name = key[0].name + yield name, field yield var_arg # normal arguments (including key arguments) else: @@ -350,6 +356,40 @@ class Execution(Executable): """ cache = {} + def process_decorators(self): + """ Returns the function, that is to be executed in the end """ + func = self.base + + # only enter it, if has not already been processed + if hasattr(func, 'is_decorated') and not func.is_decorated: + for dec in reversed(self.base.decorators): + print '\n\ndecorator:', dec, func + dec_results = follow_statement(dec) + if not len(dec_results): + debug.warning('decorator func not found', self.base) + return [] + if len(dec_results) > 1: + debug.warning('multiple decorators found', self.base, + dec_results) + decorator = dec_results.pop() + # create param array + params = parsing.Array(parsing.Array.NOARRAY, func) + params.values = [[func]] + wrappers = Execution(decorator, params).get_return_types() + if not len(wrappers): + debug.warning('no wrappers found', self.base) + return [] + if len(wrappers) > 1: + debug.warning('multiple wrappers found', self.base, + wrappers) + # this is here, that the wrapper gets executed + func = wrappers[0] + + print 'dece\n\n' + #print dec.parent + return func + + @memoize_default(default=[]) def get_return_types(self): """ @@ -361,12 +401,15 @@ class Execution(Executable): # there maybe executions of executions stmts = [Instance(self.base, self.var_args)] else: + func = self.process_decorators() + # set the callback function to get the var_args - self.set_param_cb(self.base) # don't do this with exceptions, as usual, because some deeper # exceptions could be catched - and I wouldn't know what happened. - if hasattr(self.base, 'returns'): - ret = self.base.returns + if hasattr(func, 'returns'): + self.set_param_cb(func) + self.base.is_decorated = True + ret = func.returns for s in ret: #temp, s.parent = s.parent, self stmts += follow_statement(s) @@ -375,12 +418,17 @@ class Execution(Executable): # reset the callback function on exit # TODO how can we deactivate this again? #self.base.param_cb = None + + # func could have changed because of decorators, so clear them + # again + self.base.is_decorated = False else: - debug.warning("no execution possible", self.base) + debug.warning("no execution possible", func) - debug.dbg('exec stmts=', stmts, self.base, repr(self)) - return stmts + debug.dbg('exec results:', stmts, self.base, repr(self)) + + return strip_imports(stmts) def __repr__(self): return "<%s of %s>" % \ @@ -714,7 +762,10 @@ def follow_call_list(scope, call_list): if parsing.Array.is_type(call, parsing.Array.NOARRAY): result += follow_call_list(scope, call) else: - if not isinstance(call, str): + # with things like params, these can also be functions, etc + if isinstance(call, (parsing.Function, parsing.Class)): + result.append(call) + elif not isinstance(call, str): # The string tokens are just operations (+, -, etc.) result += follow_call(scope, call) return set(result) @@ -793,10 +844,8 @@ 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('befexec', scope) - exe = Execution(scope, current) - result = strip_imports(exe.get_return_types()) - debug.dbg('exec', result) + debug.dbg('exec', scope) + result = Execution(scope, current).get_return_types() else: # curly braces are not allowed, because they make no sense debug.warning('strange function call with {}', current, scope) diff --git a/parsetest.py b/parsetest.py index 7d32da33..91c76279 100644 --- a/parsetest.py +++ b/parsetest.py @@ -158,8 +158,6 @@ a = 3; b = "" b,a=a,b a. - - class C(object): def c_a(self): self.c_b = 1 @@ -167,16 +165,16 @@ class C(object): test = [1,2] -def fu(a=1, b="", *args, **kwargs): - return a, b, args, kwargs +def dec(func): + def wrapper(*args, **kwargs): + return func(dict, *args, **kwargs) + return wrapper -exe = fu(arg2=1,arg1=set) -exe = fu(list, 1, "", c=set) -args_func() #1,"", a=list)[0]. -args_func(arg1=0, *test + [3], *[4,5], **{'a': 'b'}). +@dec +def fu(a, b, c, *args, **kwargs): + return a, b, c, args, kwargs +exe = fu(list, c=set, d='') - -exe[2][0]. -exe[3]['c'].union exe[0]. +exe[4]['d'] diff --git a/parsing.py b/parsing.py index 18b1d756..3a396627 100644 --- a/parsing.py +++ b/parsing.py @@ -104,6 +104,10 @@ class Scope(Simple): # print 'push scope: [%s@%s]' % (sub.line_nr, sub.indent) sub.parent = self sub.decorators = decorators + for d in decorators: + # the parent is the same, because the decorator has not the scope + # of the function + d.parent = sub.parent self.subscopes.append(sub) return sub @@ -308,6 +312,9 @@ class Function(Scope): for p in params: p.parent = self self.decorators = [] + # helper variable for the evaluator, maybe remove this, when + # evaluate.py is rewritten, fully functional. + self.is_decorated = False self.returns = [] self.is_generator = False diff --git a/test/completion/functions.py b/test/completion/functions.py index 8d789a3b..08de3088 100644 --- a/test/completion/functions.py +++ b/test/completion/functions.py @@ -169,3 +169,60 @@ exe[3].items exe[3]['c'].union #? [] exe[3]['c'].upper + +# ----------------- +# decorators +# ----------------- + +def decorator(func): + def wrapper(*args): + return func(1, *args) + return wrapper + +@decorator +def decorated(a,b): + return a,b + +exe = decorated(set, '') + +#? [] +exe[0].union +#? ['union'] +exe[1].union + +#? ['real'] +exe[0].real +#? [] +exe[1].real + +# more complicated with args/kwargs +def dec(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + +@dec +def fu(a, b, c, *args, **kwargs): + return a, b, c, args, kwargs + +exe = fu(list, c=set, b=3, d='') + +#? ['append'] +exe[0].append +#? ['real'] +exe[1].real +#? ['union'] +exe[2].union + +#? [] +exe[4]['d'].union +#? ['upper'] +exe[4]['d'].upper + + +exe = fu(list, set, 3, '', d='') + +#? ['upper'] +exe[3][0].upper +#? [] +exe[3][0].real