From 22da402a7a557683c76947d14d44c33a1ba8fd7e Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 22 Sep 2015 19:18:36 +0200 Subject: [PATCH] Replace the get_iterator_types function with a different interface, which enables Jedi to detect invalid for loop inputs that are not iterable. --- jedi/evaluate/finder.py | 9 ++------- jedi/evaluate/iterable.py | 12 +++++------- jedi/evaluate/stdlib.py | 16 ++++++++++------ jedi/evaluate/sys_path.py | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 60b0b60a..3219f1d1 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -300,13 +300,8 @@ class NameFinder(object): def _name_to_types(evaluator, name, scope): types = [] typ = name.get_definition() - if typ.isinstance(tree.ForStmt): - for_types = evaluator.eval_element(typ.children[3]) - for_types = iterable.get_iterator_types(for_types) - types += check_tuple_assignments(for_types, name) - elif typ.isinstance(tree.CompFor): - for_types = evaluator.eval_element(typ.children[3]) - for_types = iterable.get_iterator_types(for_types) + if typ.isinstance(tree.ForStmt, tree.CompFor): + for_types = iterable.get_iterator_types(evaluator, typ.children[3]) types += check_tuple_assignments(for_types, name) elif isinstance(typ, tree.Param): types += _eval_param(evaluator, typ, scope) diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index d24922b8..859774d1 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -379,17 +379,17 @@ class MergedArray(_FakeArray): return sum(len(a) for a in self._arrays) -def get_iterator_types(inputs): +def get_iterator_types(evaluator, element): """Returns the types of any iterator (arrays, yields, __iter__, etc).""" iterators = [] # Take the first statement (for has always only # one, remember `in`). And follow it. - for it in inputs: + for it in evaluator.eval_element(element): if isinstance(it, (Generator, Array, ArrayInstance, Comprehension)): iterators.append(it) else: if not hasattr(it, 'execute_subscope_by_name'): - analysis.add(self._evaluator, 'type-error-generator', index_array) + analysis.add(evaluator, 'type-error-not-iterable', element) debug.warning('iterator/for loop input wrong: %s', it) continue try: @@ -456,8 +456,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list): result += unite(evaluator.eval_element(node) for node in nodes) elif add_name in ['extend', 'update']: for key, nodes in params: - iterators = unite(evaluator.eval_element(node) for node in nodes) - result += get_iterator_types(iterators) + result += unite(get_iterator_types(evaluator, node) for node in nodes) return result from jedi.evaluate import representation as er, param @@ -568,8 +567,7 @@ class ArrayInstance(IterableWrapper): items = [] for key, nodes in self.var_args.unpack(): for node in nodes: - for typ in self._evaluator.eval_element(node): - items += get_iterator_types([typ]) + items += get_iterator_types(self._evaluator, node) module = self.var_args.get_parent_until() is_list = str(self.instance.name) == 'list' diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py index b25fe4cd..8e6bb933 100644 --- a/jedi/evaluate/stdlib.py +++ b/jedi/evaluate/stdlib.py @@ -148,12 +148,16 @@ def builtins_super(evaluator, types, objects, scope): return [] +def get_iterable_content(evaluator, arguments, argument_index): + nodes = list(arguments.unpack())[argument_index][1] + return tuple(iterable.unite(iterable.get_iterator_types(node) + for node in nodes)) + + @argument_clinic('sequence, /', want_obj=True, want_arguments=True) def builtins_reversed(evaluator, sequences, obj, arguments): # Unpack the iterator values - # TODO replace get_iterator_types. - #elements = list(arguments.unpack())[0][1] - objects = tuple(iterable.get_iterator_types(sequences)) + objects = get_iterable_content(evaluator, arguments, 0) rev = [iterable.AlreadyEvaluated([o]) for o in reversed(objects)] # Repack iterator values and then run it the normal way. This is # necessary, because `reversed` is a function and autocompletion @@ -165,8 +169,8 @@ def builtins_reversed(evaluator, sequences, obj, arguments): return [er.Instance(evaluator, obj, param.Arguments(evaluator, [rev]))] -@argument_clinic('obj, type, /') -def builtins_isinstance(evaluator, objects, types): +@argument_clinic('obj, type, /', want_arguments=True) +def builtins_isinstance(evaluator, objects, types, arguments): bool_results = set([]) for o in objects: try: @@ -184,7 +188,7 @@ def builtins_isinstance(evaluator, objects, types): bool_results.add(cls_or_tup in mro) else: # Check for tuples. - classes = iterable.get_iterator_types([cls_or_tup]) + classes = get_iterable_content(evaluator, arguments, 1) bool_results.add(any(cls in mro for cls in classes)) return [compiled.keyword_from_value(x) for x in bool_results] diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index 7cfbd57b..10cdb34e 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -92,7 +92,7 @@ def _paths_from_assignment(evaluator, expr_stmt): from jedi.evaluate.iterable import get_iterator_types from jedi.evaluate.precedence import is_string - for val in get_iterator_types(evaluator.eval_statement(expr_stmt)): + for val in get_iterator_types(evaluator, expr_stmt): if is_string(val): yield val.obj