2 Recursions are the recipe of |jedi| to conquer Python code. However, someone
3 must stop recursions going mad. Some settings are here to make |jedi| stop at
4 the right time. You can read more about them :ref:`here <settings-recursion>`.
6 Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not
7 thread-safe, because ``execution_recursion_decorator`` uses class variables to
8 count the function calls.
10 .. _settings-recursion:
15 Recursion settings are important if you don't want extremely
16 recursive python code to go absolutely crazy.
18 The default values are based on experiments while completing the |jedi| library
19 itself (inception!). But I don't think there's any other Python library that
20 uses recursion in a similarly extreme way. Completion should also be fast and
21 therefore the quality might not always be maximal.
23 .. autodata:: recursion_limit
24 .. autodata:: total_function_execution_limit
25 .. autodata:: per_function_execution_limit
26 .. autodata:: per_function_recursion_limit
29 from contextlib
import contextmanager
31 from jedi
import debug
32 from jedi
.inference
.base_value
import NO_VALUES
37 Like :func:`sys.getrecursionlimit()`, just for |jedi|.
39 total_function_execution_limit
= 200
41 This is a hard limit of how many non-builtin functions can be executed.
43 per_function_execution_limit
= 6
45 The maximal amount of times a specific function may be executed.
47 per_function_recursion_limit
= 2
49 A function may not be executed more than this number of times recursively.
53 class RecursionDetector
:
55 self
.pushed_nodes
= []
59 def execution_allowed(inference_state
, node
):
61 A decorator to detect recursions in statements. In a recursion a statement
62 at the same place, in the same module may not be executed two times.
64 pushed_nodes
= inference_state
.recursion_detector
.pushed_nodes
66 if node
in pushed_nodes
:
67 debug
.warning('catched stmt recursion: %s @%s', node
,
68 getattr(node
, 'start_pos', None))
72 pushed_nodes
.append(node
)
78 def execution_recursion_decorator(default
=NO_VALUES
):
80 def wrapper(self
, **kwargs
):
81 detector
= self
.inference_state
.execution_recursion_detector
82 limit_reached
= detector
.push_execution(self
)
87 result
= func(self
, **kwargs
)
89 detector
.pop_execution()
95 class ExecutionRecursionDetector
:
97 Catches recursions of executions.
99 def __init__(self
, inference_state
):
100 self
._inference
_state
= inference_state
102 self
._recursion
_level
= 0
103 self
._parent
_execution
_funcs
= []
104 self
._funcdef
_execution
_counts
= {}
105 self
._execution
_count
= 0
107 def pop_execution(self
):
108 self
._parent
_execution
_funcs
.pop()
109 self
._recursion
_level
-= 1
111 def push_execution(self
, execution
):
112 funcdef
= execution
.tree_node
114 # These two will be undone in pop_execution.
115 self
._recursion
_level
+= 1
116 self
._parent
_execution
_funcs
.append(funcdef
)
118 module_context
= execution
.get_root_context()
120 if module_context
.is_builtins_module():
121 # We have control over builtins so we know they are not recursing
122 # like crazy. Therefore we just let them execute always, because
123 # they usually just help a lot with getting good results.
126 if self
._recursion
_level
> recursion_limit
:
127 debug
.warning('Recursion limit (%s) reached', recursion_limit
)
130 if self
._execution
_count
>= total_function_execution_limit
:
131 debug
.warning('Function execution limit (%s) reached', total_function_execution_limit
)
133 self
._execution
_count
+= 1
135 if self
._funcdef
_execution
_counts
.setdefault(funcdef
, 0) >= per_function_execution_limit
:
136 if module_context
.py__name__() == 'typing':
139 'Per function execution limit (%s) reached: %s',
140 per_function_execution_limit
,
144 self
._funcdef
_execution
_counts
[funcdef
] += 1
146 if self
._parent
_execution
_funcs
.count(funcdef
) > per_function_recursion_limit
:
148 'Per function recursion limit (%s) reached: %s',
149 per_function_recursion_limit
,