diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index ab328090..fcb8797e 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -147,7 +147,7 @@ class Evaluator(object): types = self.eval_element(rhs) if seek_name: - types = finder.check_tuple_assignments(types, seek_name) + types = finder.check_tuple_assignments(self, types, seek_name) first_operation = stmt.first_operation() if first_operation not in ('=', None) and not isinstance(stmt, er.InstanceElement): # TODO don't check for this. @@ -352,21 +352,18 @@ class Evaluator(object): trailer_op, node = trailer.children[:2] if node == ')': # `arglist` is optional. node = () + new_types = set() - for typ in types: - debug.dbg('eval_trailer: %s in scope %s', trailer, typ) - if trailer_op == '.': - new_types |= self.find_types(typ, node) - elif trailer_op == '(': - new_types |= self.execute(typ, node, trailer) - elif trailer_op == '[': - try: - get = typ.get_index_types - except AttributeError: - debug.warning("TypeError: '%s' object is not subscriptable" - % typ) - else: - new_types |= get(self, node) + if trailer_op == '[': + for trailer_typ in self.eval_element(node): + new_types |= iterable.py__getitem__(self, types, trailer_typ, trailer_op) + else: + for typ in types: + debug.dbg('eval_trailer: %s in scope %s', trailer, typ) + if trailer_op == '.': + new_types |= self.find_types(typ, node) + elif trailer_op == '(': + new_types |= self.execute(typ, node, trailer) return new_types def execute_evaluated(self, obj, *args): diff --git a/jedi/evaluate/analysis.py b/jedi/evaluate/analysis.py index ef823381..10f01b8c 100644 --- a/jedi/evaluate/analysis.py +++ b/jedi/evaluate/analysis.py @@ -20,6 +20,7 @@ CODES = { 'type-error-operation': (11, TypeError, None), 'type-error-not-iterable': (12, TypeError, None), 'type-error-isinstance': (13, TypeError, None), + 'type-error-not-subscriptable': (13, TypeError, None), } diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 0a5d8a2e..3f74d964 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -302,7 +302,7 @@ def _name_to_types(evaluator, name, scope): if typ.isinstance(tree.ForStmt, tree.CompFor): container_types = evaluator.eval_element(typ.children[3]) for_types = iterable.py__iter__types(evaluator, container_types, typ.children[3]) - types = check_tuple_assignments(for_types, name) + types = check_tuple_assignments(evaluator, for_types, name) elif isinstance(typ, tree.Param): types = _eval_param(evaluator, typ, scope) elif typ.isinstance(tree.ExprStmt): @@ -542,26 +542,20 @@ def global_names_dict_generator(evaluator, scope, position): yield names_dict, None -def check_tuple_assignments(types, name): +def check_tuple_assignments(evaluator, types, name): """ Checks if tuples are assigned. """ - for index in name.assignment_indexes(): - new_types = set() - for r in types: + for index, node in name.assignment_indexes(): + iterated = iterable.py__iter__(evaluator, types, node) + all_types = set() + for _ in range(index + 1): try: - func = r.get_exact_index_types - except AttributeError: - debug.warning("Invalid tuple lookup #%s of result %s in %s", - index, types, name) - else: - if isinstance(r, iterable.Array) and r.type == 'dict': - continue - try: - new_types |= func(index) - except IndexError: - pass - types = new_types + types = next(iterated) + all_types |= types + except StopIteration: + types = all_types + break return types diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index bf31b1ad..0732415d 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -23,13 +23,12 @@ It is important to note that: from jedi.common import unite, ignored, safe_property from jedi import debug from jedi import settings -from jedi._compatibility import use_metaclass, is_py3, unicode +from jedi._compatibility import use_metaclass, unicode from jedi.parser import tree from jedi.evaluate import compiled from jedi.evaluate import helpers from jedi.evaluate.cache import CachedMetaClass, memoize_default from jedi.evaluate import analysis -from jedi.evaluate.precedence import literals_to_types class IterableWrapper(tree.Base): @@ -269,8 +268,8 @@ class Array(IterableWrapper, ArrayMixin): result |= check_array_additions(self._evaluator, self) return result - def get_exact_index_types(self, mixed_index): - """ Here the index is an int/str. Raises IndexError/KeyError """ + def py__getitem__(self, index): + """Here the index is an int/str. Raises IndexError/KeyError.""" if self.type == 'dict': for key, values in self._items(): # Because we only want the key to be a string. @@ -278,13 +277,13 @@ class Array(IterableWrapper, ArrayMixin): for k in keys: if isinstance(k, compiled.CompiledObject) \ - and mixed_index == k.obj: + and index == k.obj: for value in values: return self._evaluator.eval_element(value) raise KeyError('No key found in dictionary %s.' % self) # Can raise an IndexError - return self._evaluator.eval_element(self._items()[mixed_index]) + return self._evaluator.eval_element(self._items()[index]) def iter_content(self): return self.values() @@ -466,19 +465,20 @@ def unpack_tuple_to_dict(evaluator, types, exprlist): raise NotImplementedError -def py__iter__(evaluator, types, node): +def py__iter__(evaluator, types, node=None): debug.dbg('py__iter__') for typ in types: try: iter_method = typ.py__iter__ except AttributeError: - analysis.add(evaluator, 'type-error-not-iterable', node) + if node is not None: + analysis.add(evaluator, 'type-error-not-iterable', node) else: for result in iter_method(): yield result -def py__iter__types(evaluator, types, node): +def py__iter__types(evaluator, types, node=None): """ Calls `py__iter__`, but ignores the ordering in the end and just returns all types that it contains. @@ -486,6 +486,33 @@ def py__iter__types(evaluator, types, node): return unite(py__iter__(evaluator, types, node)) +def py__getitem__(evaluator, types, index, node): + result = set() + + # Index handling. + if isinstance(index, compiled.CompiledObject): + pure_index = index.obj + elif not type(index) in (float, int, str, unicode): + pure_index = index + else: + # If the index is not clearly defined, we have to get all the + # possiblities. + return py__iter__types(evaluator, types) + + for typ in types: + # The actual getitem call. + try: + getitem = typ.py__getitem__ + except AttributeError: + analysis.add(evaluator, 'type-error-not-subscriptable', node) + else: + try: + result |= getitem(pure_index) + except IndexError: + return py__iter__types(evaluator, set([typ])) + return result + + def check_array_additions(evaluator, array): """ Just a mapper function for the internal _check_array_additions """ if array.type not in ('list', 'set'): diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index fe006bb5..ff0626ba 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -22,6 +22,9 @@ py__bool__() Returns True/False/None; None means that there's no certainty. py__bases__(evaluator) Returns a list of base classes. py__mro__(evaluator) Returns a list of classes (the mro). +py__iter__() Returns a generator of a set of types. +py__getitem__(index: int/str) Returns a a set of types of the index. + Can raise an IndexError/KeyError. py__getattribute__(evaluator, name) Returns a list of attribute values. The name can be str or Name. ====================================== ======================================== diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 3f38b1ea..5a871671 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -319,14 +319,14 @@ class Name(Leaf): def assignment_indexes(self): """ - Returns an array of ints of the indexes that are used in tuple - assignments. + Returns an array of tuple(int, node) of the indexes that are used in + tuple assignments. For example if the name is ``y`` in the following code:: x, (y, z) = 2, '' - would result in ``[1, 0]``. + would result in ``[(1, xyz_node), (0, yz_node)]``. """ indexes = [] node = self.parent @@ -335,7 +335,7 @@ class Name(Leaf): if is_node(node, 'testlist_comp', 'testlist_star_expr', 'exprlist'): for i, child in enumerate(node.children): if child == compare: - indexes.insert(0, int(i / 2)) + indexes.insert(0, (int(i / 2), node)) break else: raise LookupError("Couldn't find the assignment.") diff --git a/test/completion/arrays.py b/test/completion/arrays.py index 21a0eb5c..720d2ec9 100644 --- a/test/completion/arrays.py +++ b/test/completion/arrays.py @@ -209,6 +209,15 @@ dic2[r'as' 'd' u'f'] #? int() str() dic2['just_something'] +# unpacking +a, b = dic2 +#? str() +a +a, b = {1: 'x', 2.0: 1j} +#? int() float() +a + + def f(): """ github #83 """ r = {}