1
0
forked from VimPlug/jedi

class decorators with __call__ are now properly working

This commit is contained in:
David Halter
2012-06-12 17:02:24 +02:00
parent c1fc527d60
commit e1dbdc1662
3 changed files with 63 additions and 31 deletions

View File

@@ -15,6 +15,7 @@ TODO nonlocal statement
TODO getattr / __getattr__ / __getattribute__ ? TODO getattr / __getattr__ / __getattribute__ ?
TODO descriptors TODO descriptors
TODO @staticmethod @classmethod
""" """
from _compatibility import next, property from _compatibility import next, property
@@ -157,7 +158,7 @@ class Instance(Executable):
class_names = self.base.get_defined_names() class_names = self.base.get_defined_names()
for var in class_names: for var in class_names:
# functions are also instance elements # functions are also instance elements
if isinstance(var.parent, (parsing.Function, Function)): if isinstance(var.parent, (Function, parsing.Function)):
var = InstanceElement(self, var) var = InstanceElement(self, var)
names.append(var) names.append(var)
@@ -187,8 +188,12 @@ class InstanceElement(object):
self.var = var self.var = var
@property @property
@memoize_default()
def parent(self): 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): def get_parent_until(self, *classes):
scope = self.var.get_parent_until(*classes) scope = self.var.get_parent_until(*classes)
@@ -261,19 +266,21 @@ class Function(object):
def __init__(self, func, is_decorated=False): def __init__(self, func, is_decorated=False):
""" This should not be called directly """ """ This should not be called directly """
self.base_func = func self.base_func = func
self.func = self.process_decorators(is_decorated) self.is_decorated = is_decorated
def __getattr__(self, name): @property
return getattr(self.func, name) @memoize_default()
def func(self):
def process_decorators(self, is_decorated): """
""" Returns the function, that is to be executed in the end """ Returns the function, that is to be executed in the end.
func = self.base_func This is also the places where the decorators are processed.
"""
f = self.base_func
# only enter it, if has not already been processed # 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): for dec in reversed(self.base_func.decorators):
debug.dbg('decorator:', dec, func) debug.dbg('decorator:', dec, f)
dec_results = follow_statement(dec) dec_results = follow_statement(dec)
if not len(dec_results): if not len(dec_results):
debug.warning('decorator func not found', self.base_func) debug.warning('decorator func not found', self.base_func)
@@ -283,7 +290,7 @@ class Function(object):
dec_results) dec_results)
decorator = dec_results.pop() decorator = dec_results.pop()
# create param array # 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 = parsing.Array(parsing.Array.NOARRAY, old_func)
params.values = [[old_func]] params.values = [[old_func]]
wrappers = Execution(decorator, params).get_return_types() wrappers = Execution(decorator, params).get_return_types()
@@ -294,16 +301,18 @@ class Function(object):
debug.warning('multiple wrappers found', self.base_func, debug.warning('multiple wrappers found', self.base_func,
wrappers) wrappers)
# this is here, that the wrapper gets executed # this is here, that the wrapper gets executed
func = wrappers[0] f = wrappers[0]
debug.dbg('decorator end') debug.dbg('decorator end')
if func != self.base_func: if f != self.base_func and isinstance(f, parsing.Function):
return Function(func) f = Function(f)
else: return f
return func
def __getattr__(self, name):
return getattr(self.func, name)
def __repr__(self): def __repr__(self):
return "<e%s of %s>" % (self.__class__.__name__, self.func) return "<e%s of %s>" % (self.__class__.__name__, self.base_func)
class Execution(Executable): class Execution(Executable):
@@ -313,8 +322,6 @@ class Execution(Executable):
This is the most complicated class, because it contains the logic to This is the most complicated class, because it contains the logic to
transfer parameters. This is even more complicated, because there may be transfer parameters. This is even more complicated, because there may be
multiple call to functions and recursion has to be avoided. multiple call to functions and recursion has to be avoided.
TODO InstantElements ?
""" """
cache = {} cache = {}
@@ -342,10 +349,15 @@ class Execution(Executable):
except (AttributeError, KeyError): except (AttributeError, KeyError):
debug.warning("no execution possible", self.base) debug.warning("no execution possible", self.base)
else: 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) exe = Execution(call_method, self.var_args)
stmts = exe.get_return_types() 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) 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 there are results, ignore the other scopes
if result: if result:
break break
debug.dbg('sfn filter', name_str, result) debug.dbg('sfn filter "%s" in %s: %s' % (name_str, scope, result))
return result return result
if search_global: 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, scope = stmt.get_parent_until(parsing.Function, Function, Execution,
parsing.Class, Class, Instance, parsing.Class, Class, Instance,
InstanceElement) 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() 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) result = follow_call_list(scope, call_list)
# assignment checking is only important if the statement defines multiple # assignment checking is only important if the statement defines multiple
@@ -1039,7 +1050,7 @@ def follow_path(path, scope, position=None):
current = next(path) current = next(path)
except StopIteration: except StopIteration:
return None return None
debug.dbg('follow', current, scope) debug.dbg('follow %s in scope %s' % (current, scope))
result = [] result = []
if isinstance(current, parsing.Array): if isinstance(current, parsing.Array):
@@ -1048,7 +1059,7 @@ def follow_path(path, scope, position=None):
result = scope.get_index_types(current) result = scope.get_index_types(current)
elif current.type not in [parsing.Array.DICT]: elif current.type not in [parsing.Array.DICT]:
# scope must be a class or func - make an instance or execution # 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() result = Execution(scope, current).get_return_types()
else: else:
# curly braces are not allowed, because they make no sense # curly braces are not allowed, because they make no sense

View File

@@ -183,9 +183,27 @@ class B():
def r(self): def r(self):
return 1 return 1
@r.setter #@r.setter
def r(self, value): #def r(self, value):
pass # pass
#? []
B().r.
#? [] #? []
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]

View File

@@ -54,12 +54,15 @@ def run_definition_test(correct, source, line_nr, line):
else: else:
should_be = set() should_be = set()
for index in re.finditer('(?: +|$)', correct): for index in re.finditer('(?: +|$)', correct):
if correct == ' ':
continue
# -1 for the comment, +3 because of the comment start `#? ` # -1 for the comment, +3 because of the comment start `#? `
start = index.start() + 3 start = index.start() + 3
try: try:
should_be |= defs(line_nr-1, start) should_be |= defs(line_nr-1, start)
except Exception: 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. # because the objects have different ids, `repr` it, then compare it.
should_str = sorted(str(r) for r in should_be) should_str = sorted(str(r) for r in should_be)
is_str = sorted(str(r) for r in result) is_str = sorted(str(r) for r in result)