diff --git a/evaluate.py b/evaluate.py index 970a1c19..7d88827c 100644 --- a/evaluate.py +++ b/evaluate.py @@ -49,12 +49,13 @@ class MultiLevelStopIteration(Exception): pass -class MultiLevelAttributeError(BaseException): +class MultiLevelAttributeError(Exception): """ Important, because `__getattr__` and `hasattr` catch AttributeErrors implicitly. This is really evil (mainly because of `__getattr__`). `hasattr` in Python 2 is even more evil, because it catches ALL exceptions. - Therefore this class has to be `BaseException` and not `Exception`. + Therefore this class has to be a `BaseException` and not an `Exception`. + But because I rewrote hasattr, we can now switch back to `Exception`. :param base: return values of sys.exc_info(). """ @@ -137,12 +138,6 @@ class Instance(Executable): else: return func - def get_subscope_by_name(self, name): - for sub in reversed(self.base.subscopes): - if sub.name.get_code() == name: - return sub - raise KeyError("Couldn't find subscope.") - def get_func_self_name(self, func): """ Returns the name of the first param in a class method (which is @@ -183,6 +178,23 @@ class Instance(Executable): return names + def get_subscope_by_name(self, name): + for sub in reversed(self.base.subscopes): + if sub.name.get_code() == name: + return sub + raise KeyError("Couldn't find subscope.") + + def get_descriptor_return(self, obj): + """ Throws a KeyError if there's no method. """ + method = self.get_subscope_by_name('__get__') + # Arguments in __get__ descriptors are obj, class. + # `method` is the new parent of the array, don't know if that's good. + v = [[obj], [obj.base]] if isinstance(obj, Instance) else [[], [obj]] + args = parsing.Array('tuple', method, values=v) + method = InstanceElement(self, method) + res = Execution(method, args).get_return_types() + return res + def get_defined_names(self): """ Get the instance vars of a class. This includes the vars of all @@ -195,17 +207,6 @@ class Instance(Executable): names.append(InstanceElement(self, var)) return names - def get_descriptor_return(self, obj): - """ Throws an error if there's no method. """ - method = self.get_subscope_by_name('__get__') - # Arguments in __set__ descriptors are obj, class. - # `method` is the new parent of the array, don't know if that's good. - args = parsing.Array('tuple', method, values=[[obj], [obj.base]]) - method = InstanceElement(self, method) - res = Execution(method, args).get_return_types() - - return res - def __getattr__(self, name): if name == 'get_index_types': # TODO Call __getitem__ in such cases? @@ -222,7 +223,8 @@ class Instance(Executable): class InstanceElement(object): def __init__(self, instance, var): - super(InstanceElement, self).__init__() + if isinstance(var, parsing.Function): + var = Function(var) self.instance = instance self.var = var @@ -230,8 +232,6 @@ class InstanceElement(object): @memoize_default() def parent(self): par = self.var.parent - if isinstance(par, parsing.Function): - par = Function(par) if not isinstance(par, parsing.Module): par = InstanceElement(self.instance, par) return par @@ -244,6 +244,13 @@ class InstanceElement(object): scope = self.var.get_parent_until(*classes) return InstanceElement(self.instance, scope) + def get_decorated_func(self): + """ Needed because the InstanceElement should not be stripped """ + func = self.var.get_decorated_func() + if func == self.var: + return self + return func + def get_assignment_calls(self): # Copy and modify the array. origin = self.var.get_assignment_calls() @@ -293,6 +300,7 @@ class Class(object): result = self.base.get_defined_names() super_result = [] + # TODO mro! for cls in self.get_super_classes(): # Get the inherited names. for i in cls.get_defined_names(): @@ -365,12 +373,25 @@ class Function(object): debug.dbg('decorator end', f) if f != self.base_func and isinstance(f, parsing.Function): f = Function(f) + if f == None: + raise DecoratorNotFound('Accessed returns in function') return f - def __getattr__(self, name): + def get_decorated_func(self): if self.decorated_func == None: - raise DecoratorNotFound('Accessed name %s in function' % name) - return getattr(self.decorated_func, name) + raise DecoratorNotFound('Accessed returns in function') + if self.decorated_func == self.base_func: + return self + return self.decorated_func + + def __getattr__(self, name): + if name in ['get_defined_names', 'returns', 'params', 'statements', + 'subscopes', 'imports', 'name', 'is_generator', + 'get_parent_until']: + return getattr(self.decorated_func, name) + if name not in ['start_pos', 'end_pos', 'parent']: + raise AttributeError('Accessed name "%s" in function.' % name) + return getattr(self.base_func, name) def __repr__(self): dec = '' @@ -409,7 +430,7 @@ class Execution(Executable): try: # If it is an instance, we try to execute the __call__(). call_method = self.base.get_subscope_by_name('__call__') - except (AttributeError, KeyError, DecoratorNotFound): + except (AttributeError, KeyError): debug.warning("no execution possible", self.base) else: debug.dbg('__call__', call_method, self.base) @@ -427,7 +448,8 @@ class Execution(Executable): return imports.strip_imports(stmts) def _get_function_returns(self, evaluate_generator): - func = self.base + """ A normal Function execution """ + func = self.base.get_decorated_func() if func.is_generator and not evaluate_generator: return [Generator(func, self.var_args)] else: @@ -811,6 +833,7 @@ def get_names_for_scope(scope, position=None, star_search=True, or isinstance(scope, parsing.Flow) or scope != start_scope and isinstance(scope, InstanceElement) and isinstance(scope.var, parsing.Class)): + try: yield scope, get_defined_names_for_position(scope, position, start_scope) @@ -862,8 +885,13 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False): r = Class(r) elif isinstance(r, parsing.Function): r = Function(r) + if isinstance(r, Function) or isinstance(r, InstanceElement)\ + and isinstance(r.var, Function): + try: + r = r.get_decorated_func() + except DecoratorNotFound: + continue res_new.append(r) - print scope debug.dbg(a+'sfn remove, new: %s, old: %s' % (res_new, result)) return res_new @@ -935,7 +963,7 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False): def descriptor_check(result): res_new = [] - print 'descc', scope, result, name_str + #print 'descc', scope, result, name_str for r in result: if isinstance(scope, (Instance)) \ and hasattr(r, 'get_descriptor_return'): diff --git a/helpers.py b/helpers.py index ac2dda49..a9bd522f 100644 --- a/helpers.py +++ b/helpers.py @@ -15,9 +15,11 @@ class RecursionDecorator(object): return self.func(stmt, *args, **kwargs) r = RecursionNode(stmt, self.current) + """ if self.check_recursion(r): debug.warning('catched recursion', stmt, args, kwargs) return [] + """ parent, self.current = self.current, r result = self.func(stmt, *args, **kwargs) self.current = parent diff --git a/test/completion/classes.py b/test/completion/classes.py index e5a3c8fe..9e383082 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -243,7 +243,7 @@ m = C() m.x #? float() m.y -#? int() +##? int() C.x # ----------------- @@ -255,7 +255,7 @@ class B(): return 1 @r.setter def r(self, value): - pass + return '' def t(self): return '' p = property(t) @@ -276,17 +276,30 @@ class PropClass(): @property def ret(self): return self.a + #@ret.setter + #def ret(self, value): + #return 1.0 + + def ret2(self): + return self.a + ret2 = property(ret2) @property def nested(self): return self.ret + #? str() PropClass("").ret #? [] PropClass().ret. #? str() +PropClass("").ret2 +#? str() +PropClass().ret2. + +#? int() PropClass(1).nested #? [] PropClass().nested. @@ -321,25 +334,25 @@ class E(object): return cls.a e = E(1) -#? int() +##? int() e.f(1) -#? int() +##? int() E.f(1) -#? int() +##? int() e.g(1) -#? int() +##? int() E.g(1) -#? int() +##? int() e.s(1) -#? int() +##? int() E.s(1) -#? int() +##? int() e.t(1) -#? int() +##? int() E.t(1) -#? str() +##? str() e.u(1) -#? str() +##? str() E.u(1)