diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index c647badf..d5900868 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -108,7 +108,7 @@ class Evaluator(object): self.BUILTINS = compiled.get_special_object(self, 'BUILTINS') def reset_recursion_limitations(self): - self.recursion_detector = recursion.RecursionDetector(self) + self.recursion_detector = recursion.RecursionDetector() self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) def find_types(self, context, name_or_str, name_context, position=None, diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 93949113..c566b0aa 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -20,21 +20,22 @@ It is important to note that: 1. Array modfications work only in the current module. 2. Jedi only checks Array additions; ``list.pop``, etc are ignored. """ -from jedi.common import unite, safe_property from jedi import debug from jedi import settings +from jedi import common +from jedi.common import unite, safe_property from jedi._compatibility import unicode, zip_longest, is_py3 from jedi.parser import tree from jedi.evaluate import compiled from jedi.evaluate import helpers -from jedi.evaluate.cache import memoize_default from jedi.evaluate import analysis from jedi.evaluate import pep0484 -from jedi import common -from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \ - ParserTreeFilter from jedi.evaluate import context from jedi.evaluate import precedence +from jedi.evaluate import recursion +from jedi.evaluate.cache import memoize_default +from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \ + ParserTreeFilter class AbstractSequence(context.Context): @@ -774,25 +775,21 @@ def _check_array_additions(context, sequence): continue random_context = context.create_context(name) - if context.evaluator.recursion_detector.push_stmt(power): - # Check for recursion. Possible by using 'extend' in - # combination with function calls. - continue - try: - found = helpers.evaluate_call_of_leaf( - random_context, - name, - cut_own_trailer=True - ) - if sequence in found: - # The arrays match. Now add the results - added_types |= find_additions( + + with recursion.execution_allowed(context.evaluator, power) as allowed: + if allowed: + found = helpers.evaluate_call_of_leaf( random_context, - execution_trailer.children[1], - add_name + name, + cut_own_trailer=True ) - finally: - context.evaluator.recursion_detector.pop_stmt() + if sequence in found: + # The arrays match. Now add the results + added_types |= find_additions( + random_context, + execution_trailer.children[1], + add_name + ) # reset settings settings.dynamic_params_for_other_modules = temp_param_add diff --git a/jedi/evaluate/recursion.py b/jedi/evaluate/recursion.py index b158c2c6..d4a2ee92 100644 --- a/jedi/evaluate/recursion.py +++ b/jedi/evaluate/recursion.py @@ -7,75 +7,33 @@ Next to :mod:`jedi.evaluate.cache` this module also makes |jedi| not thread-safe. Why? ``execution_recursion_decorator`` uses class variables to count the function calls. """ +from contextlib import contextmanager + from jedi import debug from jedi import settings class RecursionDetector(object): + def __init__(self): + self.pushed_nodes = [] + + +@contextmanager +def execution_allowed(evaluator, node): """ 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, evaluator): - self.top = None - self.current = None - self._evaluator = evaluator + pushed_nodes = evaluator.recursion_detector.pushed_nodes - def push_stmt(self, stmt): - self.current = _RecursionNode(self._evaluator, stmt, self.current) - check = self._check_recursion() - if check: - debug.warning('catched stmt recursion: %s against %s @%s', stmt, - check.stmt, stmt.start_pos) - 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 test - if not test: - return False - - 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, evaluator, stmt, parent): - self._evaluator = evaluator - 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 = self.script == self._evaluator.BUILTINS - - 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 + if node in pushed_nodes: + debug.warning('catched stmt recursion: %s against %s @%s', node, + node.start_pos) + yield False + else: + pushed_nodes.append(node) + yield True + pushed_nodes.pop() def execution_recursion_decorator(func):