From a408fb3211d5be3b8a3e71717b86602fa3772bc6 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 18 Jul 2018 10:01:41 +0200 Subject: [PATCH] Fix a recursion error, fixes #1173 --- jedi/evaluate/dynamic.py | 22 +++++++++++++--------- jedi/evaluate/recursion.py | 10 +++++----- test/completion/recursion.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index 439bfd8e..f4b016fd 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -28,8 +28,8 @@ from jedi.evaluate.helpers import is_stdlib_path from jedi.evaluate.utils import to_list from jedi.parser_utils import get_parent_scope from jedi.evaluate.context import ModuleContext, instance -from jedi.evaluate.base_context import ContextSet - +from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS +from jedi.evaluate import recursion MAX_PARAM_SEARCHES = 20 @@ -39,11 +39,19 @@ class MergedExecutedParams(object): """ Simulates being a parameter while actually just being multiple params. """ - def __init__(self, executed_params): + + def __init__(self, evaluator, executed_params): + self.evaluator = evaluator self._executed_params = executed_params def infer(self): - return ContextSet.from_sets(p.infer() for p in self._executed_params) + with recursion.execution_allowed(self.evaluator, self) as allowed: + # We need to catch recursions that may occur, because an + # anonymous functions can create an anonymous parameter that is + # more or less self referencing. + if allowed: + return ContextSet.from_sets(p.infer() for p in self._executed_params) + return NO_CONTEXTS @debug.increase_indent @@ -90,15 +98,11 @@ def search_params(evaluator, execution_context, funcdef): string_name=string_name, ) if function_executions: - if len(function_executions) == 1: - # Don't need to wrap this one. - return function_executions[0].get_params() - zipped_params = zip(*list( function_execution.get_params() for function_execution in function_executions )) - params = [MergedExecutedParams(executed_params) for executed_params in zipped_params] + params = [MergedExecutedParams(evaluator, executed_params) for executed_params in zipped_params] # Evaluate the ExecutedParams to types. else: return create_default_params(execution_context, funcdef) diff --git a/jedi/evaluate/recursion.py b/jedi/evaluate/recursion.py index 5be3f8be..1f4f6384 100644 --- a/jedi/evaluate/recursion.py +++ b/jedi/evaluate/recursion.py @@ -65,7 +65,7 @@ def execution_allowed(evaluator, node): if node in pushed_nodes: debug.warning('catched stmt recursion: %s @%s', node, - node.start_pos) + getattr(node, 'start_pos', None)) yield False else: try: @@ -77,14 +77,14 @@ def execution_allowed(evaluator, node): def execution_recursion_decorator(default=NO_CONTEXTS): def decorator(func): - def wrapper(execution, **kwargs): - detector = execution.evaluator.execution_recursion_detector - allowed = detector.push_execution(execution) + def wrapper(self, **kwargs): + detector = self.evaluator.execution_recursion_detector + allowed = detector.push_execution(self) try: if allowed: result = default else: - result = func(execution, **kwargs) + result = func(self, **kwargs) finally: detector.pop_execution() return result diff --git a/test/completion/recursion.py b/test/completion/recursion.py index a3e7670b..ebbd69e3 100644 --- a/test/completion/recursion.py +++ b/test/completion/recursion.py @@ -76,3 +76,18 @@ class InstanceAttributeIfs: InstanceAttributeIfs().a1 #? int() str() InstanceAttributeIfs().a2 + + + +class A: + def a(self, b): + for x in [self.a(i) for i in b]: + #? + x + +class B: + def a(self, b): + for i in b: + for i in self.a(i): + #? + yield i