diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index 340e0779..0f0413ac 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -64,7 +64,7 @@ class Context(BaseContext): return self.execute(arguments) def iterate(self, contextualized_node=None, is_async=False): - debug.dbg('iterate') + debug.dbg('iterate %s', self) try: if is_async: iter_method = self.py__aiter__ diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index 49ad3e9f..2d935c75 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -212,6 +212,8 @@ class CompiledInstance(AbstractInstanceContext): super(CompiledInstance, self).__init__(*args, **kwargs) # I don't think that dynamic append lookups should happen here. That # sounds more like something that should go to py__iter__. + self._original_var_args = self.var_args + if self.class_context.name.string_name in ['list', 'set'] \ and self.parent_context.get_root_context() == self.evaluator.builtins_module: # compare the module path with the builtin name. @@ -227,6 +229,13 @@ class CompiledInstance(AbstractInstanceContext): else: return super(CompiledInstance, self).create_instance_context(class_context, node) + def get_first_non_keyword_argument_contexts(self): + key, lazy_context = next(self._original_var_args.unpack(), ('', None)) + if key is not None: + return None + + return lazy_context.infer() + class TreeInstance(AbstractInstanceContext): def __init__(self, evaluator, parent_context, class_context, var_args): diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 7947b903..6e64eb94 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -257,7 +257,21 @@ def eval_atom(context, atom): @_limit_context_infers def eval_expr_stmt(context, stmt, seek_name=None): with recursion.execution_allowed(context.evaluator, stmt) as allowed: - if allowed or context.get_root_context() == context.evaluator.builtins_module: + # Here we allow list/set to recurse under certain conditions. To make + # it possible to resolve stuff like list(set(list(x))), this is + # necessary. + if not allowed and context.get_root_context() == context.evaluator.builtins_module: + try: + instance = context.instance + except AttributeError: + pass + else: + if instance.name.string_name in ('list', 'set'): + c = instance.get_first_non_keyword_argument_contexts() + if instance not in c: + allowed = True + + if allowed: return _eval_expr_stmt(context, stmt, seek_name) return NO_CONTEXTS diff --git a/test/completion/arrays.py b/test/completion/arrays.py index 7a1f22da..857c9aec 100644 --- a/test/completion/arrays.py +++ b/test/completion/arrays.py @@ -401,6 +401,18 @@ for x in [1] + ['']: #? int() str() x +# ----------------- +# Potential Recursion Issues +# ----------------- +class X(): + def y(self): + self.a = [1] + + def x(self): + self.a = list(self.a) + #? int() + self.a[0] + # ----------------- # For loops with attribute assignment. # -----------------