File: recursion.py

package info (click to toggle)
python-jedi 0.10.0~git1%2Bf05c071-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,064 kB
  • ctags: 3,014
  • sloc: python: 16,997; makefile: 149; ansic: 13
file content (157 lines) | stat: -rw-r--r-- 5,127 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
Recursions are the recipe of |jedi| to conquer Python code. However, someone
must stop recursions going mad. Some settings are here to make |jedi| stop at
the right time. You can read more about them :ref:`here <settings-recursion>`.

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 jedi import debug
from jedi import settings
from jedi.evaluate import iterable


def recursion_decorator(func):
    def run(evaluator, stmt, *args, **kwargs):
        rec_detect = evaluator.recursion_detector
        if rec_detect.push_stmt(stmt):
            return set()
        else:
            result = func(evaluator, stmt, *args, **kwargs)
            rec_detect.pop_stmt()
        return result
    return run


class RecursionDetector(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, evaluator):
        self.top = None
        self.current = None
        self._evaluator = evaluator

    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


def execution_recursion_decorator(func):
    def run(execution, **kwargs):
        detector = execution._evaluator.execution_recursion_detector
        if detector.push_execution(execution):
            result = set()
        else:
            result = func(execution, **kwargs)
        detector.pop_execution()
        return result

    return run


class ExecutionRecursionDetector(object):
    """
    Catches recursions of executions.
    """
    def __init__(self, evaluator):
        self.recursion_level = 0
        self.parent_execution_funcs = []
        self.execution_funcs = set()
        self.execution_count = 0
        self._evaluator = evaluator

    def __call__(self, execution):
        debug.dbg('Execution recursions: %s', execution, self.recursion_level,
                  self.execution_count, len(self.execution_funcs))
        if self.check_recursion(execution):
            result = set()
        else:
            result = self.func(execution)
        self.pop_execution()
        return result

    def pop_execution(self):
        self.parent_execution_funcs.pop()
        self.recursion_level -= 1

    def push_execution(self, execution):
        in_par_execution_funcs = execution.base in self.parent_execution_funcs
        in_execution_funcs = execution.base in self.execution_funcs
        self.recursion_level += 1
        self.execution_count += 1
        self.execution_funcs.add(execution.base)
        self.parent_execution_funcs.append(execution.base)

        if self.execution_count > settings.max_executions:
            return True

        if isinstance(execution.base, (iterable.Array, iterable.Generator)):
            return False
        module = execution.get_parent_until()
        if module == self._evaluator.BUILTINS:
            return False

        if in_par_execution_funcs:
            if self.recursion_level > settings.max_function_recursion_level:
                return True
        if in_execution_funcs and \
                len(self.execution_funcs) > settings.max_until_execution_unique:
            return True
        if self.execution_count > settings.max_executions_without_builtins:
            return True
        return False