diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 39b7998d..d65c07fa 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -33,17 +33,16 @@ from jedi.evaluate import pep0484 from jedi import common from jedi.evaluate.filters import DictFilter from jedi.evaluate import context +from jedi.evaluate import precedence class AbstractSequence(context.Context): - _array_type = None - def get_filters(self, search_global, until_position=None, origin_scope=None): raise NotImplementedError @property def name(self): - return compiled.CompiledContextName(self, self._array_type) + return compiled.CompiledContextName(self, self.array_type) class IterableWrapper(tree.Base): @@ -269,7 +268,6 @@ class ArrayMixin(object): def name(self): return FakeSequence(self._evaluator, [], self.type).name - @memoize_default() def dict_values(self): return unite(self._evaluator.eval_element(v) for k, v in self._items()) @@ -352,16 +350,16 @@ class ArrayLiteralContext(AbstractSequence, ArrayMixin): self._defining_context = defining_context if self.atom.type in ('testlist_star_expr', 'testlist'): - self._array_type = 'tuple' + self.array_type = 'tuple' else: - self._array_type = ArrayLiteralContext.mapping[atom.children[0]] + self.array_type = ArrayLiteralContext.mapping[atom.children[0]] """The builtin name of the array (list, set, tuple or dict).""" c = self.atom.children array_node = c[1] - if self._array_type == 'dict' and array_node != '}' \ + if self.array_type == 'dict' and array_node != '}' \ and (not hasattr(array_node, 'children') or ':' not in array_node.children): - self._array_type = 'set' + self.array_type = 'set' def py__getitem__(self, index): """Here the index is an int/str. Raises IndexError/KeyError.""" @@ -437,6 +435,16 @@ class ArrayLiteralContext(AbstractSequence, ArrayMixin): else: return [array_node] + def exact_key_items(self): + """ + Returns a generator of tuples like dict.items(), where the key is + resolved (as a string) and the values are still LazyContexts. + """ + for key_node, value in self._items(): + for key in self._defining_context.eval_node(key_node): + if precedence.is_string(key): + yield key.obj, context.LazyTreeContext(self._defining_context, value) + def __repr__(self): return "<%s of %s>" % (type(self).__name__, self.atom) @@ -444,7 +452,7 @@ class ArrayLiteralContext(AbstractSequence, ArrayMixin): class _FakeArray(ArrayLiteralContext): def __init__(self, evaluator, container, type): # TODO is this class really needed? - self._array_type = type + self.array_type = type self._evaluator = evaluator self.atom = container self.parent_context = evaluator.BUILTINS @@ -522,12 +530,18 @@ class FakeDict(_FakeArray): def py__getitem__(self, index): return self._dct[index].infer() + def dict_values(self): + return unite(lazy_context.infer() for lazy_context in self._dct.values()) + def _items(self): raise DeprecationWarning for key, values in self._dct.items(): # TODO this is not proper. The values could be multiple values?! yield key, values[0] + def exact_key_items(self): + return self._dct.items() + class MergedArray(_FakeArray): def __init__(self, evaluator, arrays): @@ -645,7 +659,7 @@ def py__getitem__(evaluator, context, types, trailer): # If the index is not clearly defined, we have to get all the # possiblities. for typ in list(types): - if isinstance(typ, Array) and typ.type == 'dict': + if isinstance(typ, AbstractSequence) and typ.type == 'dict': types.remove(typ) result |= typ.dict_values() return result | py__iter__types(evaluator, types) diff --git a/jedi/evaluate/param.py b/jedi/evaluate/param.py index 17ec2b4d..9f8b01a9 100644 --- a/jedi/evaluate/param.py +++ b/jedi/evaluate/param.py @@ -140,11 +140,8 @@ class TreeArguments(AbstractArguments): yield None, context.get_merged_lazy_context(values) elif stars == 2: arrays = self._evaluator.eval_element(self._context, el) - dicts = [_star_star_dict(self._evaluator, a, el, func) - for a in arrays] - for dct in dicts: - for key, values in dct.items(): - raise NotImplementedError + for dct in arrays: + for key, values in _star_star_dict(self._evaluator, dct, el, func): yield key, values else: if tree.is_node(el, 'argument'): @@ -394,28 +391,19 @@ def _iterate_star_args(evaluator, array, input_node, func=None): def _star_star_dict(evaluator, array, input_node, func): - dct = defaultdict(lambda: []) from jedi.evaluate.representation import Instance if isinstance(array, Instance) and array.name.get_code() == 'dict': # For now ignore this case. In the future add proper iterators and just # make one call without crazy isinstance checks. return {} - - if isinstance(array, iterable.FakeDict): - return array._dct - elif isinstance(array, iterable.Array) and array.type == 'dict': - # TODO bad call to non-public API - for key_node, value in array._items(): - for key in evaluator.eval_element(key_node): - if precedence.is_string(key): - dct[key.obj].append(value) - + elif isinstance(array, iterable.AbstractSequence) and array.array_type == 'dict': + return array.exact_key_items() else: if func is not None: m = "TypeError: %s argument after ** must be a mapping, not %s" \ % (func.name.value, array) analysis.add(evaluator, 'type-error-star-star', input_node, message=m) - return dict(dct) + return {} def _error_argument_count(func, actual_count):