from collections import defaultdict from itertools import chain from jedi._compatibility import unicode, zip_longest from jedi import debug from jedi import common from jedi.parser import tree from jedi.evaluate import iterable from jedi.evaluate import analysis from jedi.evaluate import precedence def try_iter_content(types, depth=0): """Helper method for static analysis.""" if depth > 10: # It's possible that a loop has references on itself (especially with # CompiledObject). Therefore don't loop infinitely. return for typ in types: try: f = typ.py__iter__ except AttributeError: pass else: for iter_types in f(): try_iter_content(iter_types, depth + 1) class AbstractArguments(tree.Base): def get_parent_until(self, *args, **kwargs): raise DeprecationWarning if self.trailer is None: try: element = self.argument_node[0] if isinstance(element, iterable.AlreadyEvaluated): element = list(self._evaluator.eval_element(self._context, element))[0] except IndexError: return None else: return element.get_parent_until(*args, **kwargs) else: return self.trailer.get_parent_until(*args, **kwargs) def eval_argument_clinic(self, arguments): """Uses a list with argument clinic information (see PEP 436).""" raise DeprecationWarning('not sure if we really deprecate it') iterator = self.unpack() for i, (name, optional, allow_kwargs) in enumerate(arguments): key, va_values = next(iterator, (None, [])) if key is not None: raise NotImplementedError if not va_values and not optional: debug.warning('TypeError: %s expected at least %s arguments, got %s', name, len(arguments), i) raise ValueError values = set(chain.from_iterable(self._evaluator.eval_element(self._context, el) for el in va_values)) if not values and not optional: # For the stdlib we always want values. If we don't get them, # that's ok, maybe something is too hard to resolve, however, # we will not proceed with the evaluation of that function. debug.warning('argument_clinic "%s" not resolvable.', name) raise ValueError yield values def scope(self): raise DeprecationWarning # Returns the scope in which the arguments are used. return (self.trailer or self.argument_node).get_parent_until(tree.IsScope) def eval_args(self): # TODO this method doesn't work with named args and a lot of other # things. Use unpack. raise DeprecationWarning return [self._evaluator.eval_element(self._context, el) for stars, el in self._split()] def eval_all(self, func=None): """ Evaluates all arguments as a support for static analysis (normally Jedi). """ for key, element_values in self.unpack(): for element in element_values: types = self._evaluator.eval_element(self._context, element) try_iter_content(types) class TreeArguments(AbstractArguments): def __init__(self, evaluator, context, argument_node, trailer=None): """ The argument_node is either a parser node or a list of evaluated objects. Those evaluated objects may be lists of evaluated objects themselves (one list for the first argument, one for the second, etc). :param argument_node: May be an argument_node or a list of nodes. """ self.argument_node = argument_node self._context = context self._evaluator = evaluator self.trailer = trailer # Can be None, e.g. in a class definition. def _split(self): if isinstance(self.argument_node, (tuple, list)): for el in self.argument_node: yield 0, el else: if not (tree.is_node(self.argument_node, 'arglist') or ( # in python 3.5 **arg is an argument, not arglist (tree.is_node(self.argument_node, 'argument') and self.argument_node.children[0] in ('*', '**')))): yield 0, self.argument_node return iterator = iter(self.argument_node.children) for child in iterator: if child == ',': continue elif child in ('*', '**'): yield len(child.value), next(iterator) elif tree.is_node(child, 'argument') and \ child.children[0] in ('*', '**'): assert len(child.children) == 2 yield len(child.children[0].value), child.children[1] else: yield 0, child def unpack(self, func=None): named_args = [] for stars, el in self._split(): if stars == 1: arrays = self._evaluator.eval_element(self._context, el) iterators = [_iterate_star_args(self._evaluator, a, el, func) for a in arrays] iterators = list(iterators) for values in list(zip_longest(*iterators)): yield None, MergedLazyContexts(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(): yield key, values else: if tree.is_node(el, 'argument'): c = el.children if len(c) == 3: # Keyword argument. named_args.append((c[0].value, LazyContext(self._context, c[2]),)) else: # Generator comprehension. # Include the brackets with the parent. comp = iterable.GeneratorComprehension( self._evaluator, self.argument_node.parent) yield None, KnownContext(comp) else: yield None, LazyContext(self._context, el) # Reordering var_args is necessary, because star args sometimes appear # after named argument, but in the actual order it's prepended. for named_arg in named_args: yield named_arg def as_tuple(self): raise DeprecationWarning for stars, argument in self._split(): if tree.is_node(argument, 'argument'): argument, default = argument.children[::2] else: default = None yield argument, default, stars def __repr__(self): return '<%s: %s>' % (type(self).__name__, self.argument_node) def get_calling_var_args(self): return _get_calling_var_args(self._evaluator, self) class KnownContext(object): def __init__(self, value): self._value = value def infer(self): return set([self._value]) class KnownContexts(object): def __init__(self, values): self._values = values def infer(self): return self._values class UnknownContext(object): def infer(self): return set() class LazyContext(object): def __init__(self, context, node): self._context = context self._node = node def infer(self): return self._context.eval_node(self._node) class MergedLazyContexts(object): def __init__(self, lazy_contexts): self._lazy_contexts = lazy_contexts def infer(self): return common.unite(l.infer() for l in self._lazy_contexts) class ValueArguments(AbstractArguments): def __init__(self, value_list): self._value_list = value_list def unpack(self, func=None): for value in self._value_list: yield None, KnownContext(value) def get_calling_var_args(self): return None def __repr__(self): return '<%s: %s>' % (type(self).__name__, self._value_list) class ExecutedParam(object): """Fake a param and give it values.""" def __init__(self, original_param, var_args, lazy_context): assert not isinstance(lazy_context, (tuple, list)) self._original_param = original_param self.var_args = var_args self._lazy_context = lazy_context self.string_name = self._original_param.name.value def infer(self): return self._lazy_context.infer() @property def position_nr(self): # Need to use the original logic here, because it uses the parent. return self._original_param.position_nr def _get_calling_var_args(evaluator, var_args): old_var_args = None while var_args != old_var_args: old_var_args = var_args continue#TODO REMOVE for name, default, stars in reversed(list(var_args.as_tuple())): if not stars or not isinstance(name, tree.Name): continue names = evaluator.goto(name) if len(names) != 1: break param = names[0].get_definition() if not isinstance(param, ExecutedParam): if isinstance(param, tree.Param): # There is no calling var_args in this case - there's just # a param without any input. return None break # We never want var_args to be a tuple. This should be enough for # now, we can change it later, if we need to. if isinstance(param.var_args, Arguments): var_args = param.var_args return var_args.argument_node or var_args.trailer def get_params(evaluator, parent_context, func, var_args): result_params = [] param_dict = {} for param in func.params: param_dict[str(param.name)] = param unpacked_va = list(var_args.unpack(func)) from jedi.evaluate.representation import InstanceElement if isinstance(func, InstanceElement): raise DeprecationWarning # Include self at this place. unpacked_va.insert(0, (None, [func.instance])) var_arg_iterator = common.PushBackIterator(iter(unpacked_va)) non_matching_keys = defaultdict(lambda: []) keys_used = {} keys_only = False had_multiple_value_error = False for param in func.params: # The value and key can both be null. There, the defaults apply. # args / kwargs will just be empty arrays / dicts, respectively. # Wrong value count is just ignored. If you try to test cases that are # not allowed in Python, Jedi will maybe not show any completions. default = None if param.default is None else LazyContext(parent_context, param.default) key, argument = next(var_arg_iterator, (None, default)) while key is not None: keys_only = True k = unicode(key) try: key_param = param_dict[unicode(key)] except KeyError: non_matching_keys[key] = argument else: result_params.append(ExecutedParam(key_param, var_args, argument)) if k in keys_used: had_multiple_value_error = True m = ("TypeError: %s() got multiple values for keyword argument '%s'." % (func.name, k)) calling_va = _get_calling_var_args(evaluator, var_args) if calling_va is not None: analysis.add(evaluator, 'type-error-multiple-values', calling_va, message=m) else: try: keys_used[k] = result_params[-1] except IndexError: # TODO this is wrong stupid and whatever. pass key, argument = next(var_arg_iterator, (None, None)) if param.stars == 1: # *args param values_list = [] if argument is not None: values_list.append([argument]) for key, argument in var_arg_iterator: # Iterate until a key argument is found. if key: var_arg_iterator.push_back((key, argument)) break values_list.append([argument]) seq = iterable.FakeSequence(evaluator, 'tuple', values_list) result_arg = KnownContext(seq) elif param.stars == 2: # **kwargs param dct = iterable.FakeDict(evaluator, dict(non_matching_keys)) result_arg = KnownContext(dct) non_matching_keys = {} else: # normal param if argument is None: # No value: Return an empty container result_arg = UnknownContext() if not keys_only: calling_va = var_args.get_calling_var_args() if calling_va is not None: m = _error_argument_count(func, len(unpacked_va)) analysis.add(evaluator, 'type-error-too-few-arguments', calling_va, message=m) else: result_arg = argument # Now add to result if it's not one of the previously covered cases. if (not keys_only or param.stars == 2): result_params.append(ExecutedParam(param, var_args, result_arg)) keys_used[unicode(param.name)] = result_params[-1] if keys_only: # All arguments should be handed over to the next function. It's not # about the values inside, it's about the names. Jedi needs to now that # there's nothing to find for certain names. for k in set(param_dict) - set(keys_used): param = param_dict[k] result_arg = (UnknownContext() if param.default is None else LazyContext(parent_context, param.default)) result_params.append(ExecutedParam(param, var_args, result_arg)) if not (non_matching_keys or had_multiple_value_error or param.stars or param.default): # add a warning only if there's not another one. calling_va = _get_calling_var_args(evaluator, var_args) if calling_va is not None: m = _error_argument_count(func, len(unpacked_va)) analysis.add(evaluator, 'type-error-too-few-arguments', calling_va, message=m) for key, argument in non_matching_keys.items(): m = "TypeError: %s() got an unexpected keyword argument '%s'." \ % (func.name, key) analysis.add(evaluator, 'type-error-keyword-argument', argument.whatever, message=m) remaining_params = list(var_arg_iterator) if remaining_params: m = _error_argument_count(func, len(unpacked_va)) # Just report an error for the first param that is not needed (like # cPython). first_key, first_values = remaining_params[0] for v in first_values: if first_key is not None: # Is a keyword argument, return the whole thing instead of just # the value node. v = v.parent try: non_kw_param = keys_used[first_key] except KeyError: pass else: origin_args = non_kw_param.parent.var_args.argument_node # TODO calculate the var_args tree and check if it's in # the tree (if not continue). # print('\t\tnonkw', non_kw_param.parent.var_args.argument_node, ) if origin_args not in [f.parent.parent for f in first_values]: continue analysis.add(evaluator, 'type-error-too-many-arguments', v, message=m) return result_params def _iterate_star_args(evaluator, array, input_node, func=None): from jedi.evaluate.representation import Instance if isinstance(array, iterable.AbstractSequence): raise DeprecationWarning('_items? seriously?') # TODO ._items is not the call we want here. Replace in the future. for node in array._items(): yield node elif isinstance(array, iterable.Generator): for types in array.py__iter__(): yield KnownContexts(types) elif isinstance(array, Instance) and array.name.get_code() == 'tuple': debug.warning('Ignored a tuple *args input %s' % array) else: if func is not None: m = "TypeError: %s() argument after * must be a sequence, not %s" \ % (func.name.value, array) analysis.add(evaluator, 'type-error-star', input_node, message=m) 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) 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) def _error_argument_count(func, actual_count): default_arguments = sum(1 for p in func.params if p.default or p.stars) if default_arguments == 0: before = 'exactly ' else: before = 'from %s to ' % (len(func.params) - default_arguments) return ('TypeError: %s() takes %s%s arguments (%s given).' % (func.name, before, len(func.params), actual_count))