diff --git a/jedi/api_classes.py b/jedi/api_classes.py index 4e1ce199..dfff964a 100644 --- a/jedi/api_classes.py +++ b/jedi/api_classes.py @@ -11,7 +11,7 @@ import warnings from _compatibility import unicode import cache import dynamic -import helpers +import recursion import settings import evaluate import imports @@ -27,7 +27,7 @@ def _clear_caches(): """ cache.clear_caches() dynamic.search_param_cache.clear() - helpers.ExecutionRecursionDecorator.reset() + recursion.ExecutionRecursionDecorator.reset() evaluate.follow_statement.reset() diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 93d52fec..3c8cb69a 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -81,6 +81,7 @@ import evaluate_representation as er import debug import builtin import imports +import recursion import helpers import dynamic import docstrings @@ -522,7 +523,7 @@ def assign_tuples(tup, results, seek_name): return result -@helpers.RecursionDecorator +@recursion.RecursionDecorator @cache.memoize_default(default=[]) def follow_statement(stmt, seek_name=None): """ diff --git a/jedi/evaluate_representation.py b/jedi/evaluate_representation.py index ed48f6b6..46b67a09 100644 --- a/jedi/evaluate_representation.py +++ b/jedi/evaluate_representation.py @@ -20,6 +20,7 @@ import cache import builtin import dynamic import helpers +import recursion import debug import evaluate import common @@ -388,7 +389,7 @@ class Execution(Executable): responsibility of the decorators. """ @cache.memoize_default(default=[]) - @helpers.ExecutionRecursionDecorator + @recursion.ExecutionRecursionDecorator def get_return_types(self, evaluate_generator=False): """ Get the return types of a function. """ stmts = [] diff --git a/jedi/helpers.py b/jedi/helpers.py index 454b40cb..b276a5dd 100644 --- a/jedi/helpers.py +++ b/jedi/helpers.py @@ -1,146 +1,6 @@ import copy import parsing_representation as pr -import evaluate_representation as er -import debug -import builtin -import settings - - -class RecursionDecorator(object): - """ - A decorator to detect recursions in statements. In a recursion a statement - at the same place, in the same module may not be executed two times. - """ - def __init__(self, func): - self.func = func - self.reset() - - def __call__(self, stmt, *args, **kwargs): - #print stmt, len(self.node_statements()) - if self.push_stmt(stmt): - return [] - else: - result = self.func(stmt, *args, **kwargs) - self.pop_stmt() - return result - - def push_stmt(self, stmt): - self.current = RecursionNode(stmt, self.current) - if self._check_recursion(): - debug.warning('catched recursion', stmt) - self.pop_stmt() - return True - return False - - def pop_stmt(self): - if self.current is not None: - # I don't know how current can be None, but sometimes it happens - # with Python3. - self.current = self.current.parent - - def _check_recursion(self): - test = self.current - while True: - test = test.parent - if self.current == test: - return True - if not test: - return False - - def reset(self): - self.top = None - self.current = None - - def node_statements(self): - result = [] - n = self.current - while n: - result.insert(0, n.stmt) - n = n.parent - return result - - -class RecursionNode(object): - """ A node of the RecursionDecorator. """ - def __init__(self, stmt, parent): - self.script = stmt.get_parent_until() - self.position = stmt.start_pos - self.parent = parent - self.stmt = stmt - - # Don't check param instances, they are not causing recursions - # The same's true for the builtins, because the builtins are really - # simple. - self.is_ignored = isinstance(stmt, pr.Param) \ - or (self.script == builtin.Builtin.scope) - - def __eq__(self, other): - if not other: - return None - return self.script == other.script \ - and self.position == other.position \ - and not self.is_ignored and not other.is_ignored - - -class ExecutionRecursionDecorator(object): - """ - Catches recursions of executions. - It is designed like a Singelton. Only one instance should exist. - """ - def __init__(self, func): - self.func = func - self.reset() - - def __call__(self, execution, evaluate_generator=False): - debug.dbg('Execution recursions: %s' % execution, self.recursion_level, - self.execution_count, len(self.execution_funcs)) - if self.check_recursion(execution, evaluate_generator): - result = [] - else: - result = self.func(execution, evaluate_generator) - self.cleanup() - return result - - @classmethod - def cleanup(cls): - cls.parent_execution_funcs.pop() - cls.recursion_level -= 1 - - @classmethod - def check_recursion(cls, execution, evaluate_generator): - in_par_execution_funcs = execution.base in cls.parent_execution_funcs - in_execution_funcs = execution.base in cls.execution_funcs - cls.recursion_level += 1 - cls.execution_count += 1 - cls.execution_funcs.add(execution.base) - cls.parent_execution_funcs.append(execution.base) - - if cls.execution_count > settings.max_executions: - return True - - if isinstance(execution.base, (er.Generator, er.Array)): - return False - module = execution.get_parent_until() - if evaluate_generator or module == builtin.Builtin.scope: - return False - - if in_par_execution_funcs: - if cls.recursion_level > settings.max_function_recursion_level: - return True - if in_execution_funcs and \ - len(cls.execution_funcs) > settings.max_until_execution_unique: - return True - if cls.execution_count > settings.max_executions_without_builtins: - return True - return False - - @classmethod - def reset(cls): - cls.recursion_level = 0 - cls.parent_execution_funcs = [] - cls.execution_funcs = set() - cls.execution_count = 0 def fast_parent_copy(obj): diff --git a/jedi/recursion.py b/jedi/recursion.py new file mode 100644 index 00000000..837c15df --- /dev/null +++ b/jedi/recursion.py @@ -0,0 +1,141 @@ +import parsing_representation as pr +import evaluate_representation as er +import debug +import builtin +import settings + + +class RecursionDecorator(object): + """ + A decorator to detect recursions in statements. In a recursion a statement + at the same place, in the same module may not be executed two times. + """ + def __init__(self, func): + self.func = func + self.reset() + + def __call__(self, stmt, *args, **kwargs): + #print stmt, len(self.node_statements()) + if self.push_stmt(stmt): + return [] + else: + result = self.func(stmt, *args, **kwargs) + self.pop_stmt() + return result + + def push_stmt(self, stmt): + self.current = RecursionNode(stmt, self.current) + if self._check_recursion(): + debug.warning('catched recursion', stmt) + self.pop_stmt() + return True + return False + + def pop_stmt(self): + if self.current is not None: + # I don't know how current can be None, but sometimes it happens + # with Python3. + self.current = self.current.parent + + def _check_recursion(self): + test = self.current + while True: + test = test.parent + if self.current == test: + return True + if not test: + return False + + def reset(self): + self.top = None + self.current = None + + def node_statements(self): + result = [] + n = self.current + while n: + result.insert(0, n.stmt) + n = n.parent + return result + + +class RecursionNode(object): + """ A node of the RecursionDecorator. """ + def __init__(self, stmt, parent): + self.script = stmt.get_parent_until() + self.position = stmt.start_pos + self.parent = parent + self.stmt = stmt + + # Don't check param instances, they are not causing recursions + # The same's true for the builtins, because the builtins are really + # simple. + self.is_ignored = isinstance(stmt, pr.Param) \ + or (self.script == builtin.Builtin.scope) + + def __eq__(self, other): + if not other: + return None + return self.script == other.script \ + and self.position == other.position \ + and not self.is_ignored and not other.is_ignored + + +class ExecutionRecursionDecorator(object): + """ + Catches recursions of executions. + It is designed like a Singelton. Only one instance should exist. + """ + def __init__(self, func): + self.func = func + self.reset() + + def __call__(self, execution, evaluate_generator=False): + debug.dbg('Execution recursions: %s' % execution, self.recursion_level, + self.execution_count, len(self.execution_funcs)) + if self.check_recursion(execution, evaluate_generator): + result = [] + else: + result = self.func(execution, evaluate_generator) + self.cleanup() + return result + + @classmethod + def cleanup(cls): + cls.parent_execution_funcs.pop() + cls.recursion_level -= 1 + + @classmethod + def check_recursion(cls, execution, evaluate_generator): + in_par_execution_funcs = execution.base in cls.parent_execution_funcs + in_execution_funcs = execution.base in cls.execution_funcs + cls.recursion_level += 1 + cls.execution_count += 1 + cls.execution_funcs.add(execution.base) + cls.parent_execution_funcs.append(execution.base) + + if cls.execution_count > settings.max_executions: + return True + + if isinstance(execution.base, (er.Generator, er.Array)): + return False + module = execution.get_parent_until() + if evaluate_generator or module == builtin.Builtin.scope: + return False + + if in_par_execution_funcs: + if cls.recursion_level > settings.max_function_recursion_level: + return True + if in_execution_funcs and \ + len(cls.execution_funcs) > settings.max_until_execution_unique: + return True + if cls.execution_count > settings.max_executions_without_builtins: + return True + return False + + @classmethod + def reset(cls): + cls.recursion_level = 0 + cls.parent_execution_funcs = [] + cls.execution_funcs = set() + cls.execution_count = 0