import copy 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 representation as pr from jedi.evaluate import iterable from jedi.evaluate import helpers from jedi.evaluate import analysis from jedi.evaluate import precedence class Arguments(object): def __init__(self, evaluator, argument_node): """ The argument_node is either a parser node or a list of evaluated objects. """ self._argument_node = argument_node self._evaluator = evaluator def _split(self): if isinstance(self._argument_node, (tuple, list)): for el in self._argument_node: yield 0, el else: if not pr.is_node(self._argument_node, 'arglist'): 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) else: yield 0, child def unpack(self): named_args = [] for stars, el in self._split(): if stars == 1: arrays = self._evaluator.eval_element(el) iterators = [_iterate_star_args(self._evaluator, a, None, None) for a in arrays] iterators = list(iterators) for values in list(zip_longest(*iterators, fillvalue=())): yield None, tuple(chain.from_iterable(values)) elif stars == 2: arrays = self._evaluator.eval_element(el) dicts = [_star_star_dict(self._evaluator, a, None, None) for a in arrays] for dct in dicts: for key, values in dct.items(): yield key, values else: if pr.is_node(el, 'argument'): named_args.append((el.children[0].value, (el.children[2],))) else: yield None, (el,) # Reordering var_args is necessary, because star args sometimes appear # after named argument, but in the actual order it's prepended. for key_arg in named_args: # TODO its always only one value? yield key_arg def _reorder_var_args(var_args): named_index = None new_args = [] for i, stmt in enumerate(var_args): if isinstance(stmt, pr.Statement): if named_index is None and stmt.assignment_details: named_index = i if named_index is not None: expression_list = stmt.expression_list() if expression_list and expression_list[0] == '*': new_args.insert(named_index, stmt) named_index += 1 continue new_args.append(stmt) return new_args def kwargs(self): return [] def args(self): return [] def eval_args(self): return [self._evaluator.eval_element(el) for stars, el in self._split()] def __repr__(self): return '<%s: %s>' % (type(self).__name__, self._argument_node) class ExecutedParam(pr.Param): def __init__(self, values): """Don't use this method, it's just here to overwrite the old one.""" self.values = values @classmethod def from_param(cls, values, param, parent, var_args): instance = cls(values) before = () for cls in param.__class__.__mro__: with common.ignored(AttributeError): if before == cls.__slots__: continue before = cls.__slots__ for name in before: setattr(instance, name, getattr(param, name)) instance.original_param = param instance.is_generated = True instance.parent = parent instance.var_args = var_args return instance def eval(self, evaluator): types = [] for v in self.values: if isinstance(v, (pr.Simple, pr.Name, pr.Literal)): types += evaluator.eval_element(v) else: types.append(v) return types class Container(object): def __init__(self, values): self.values = values def eval(self, evaluator): return self.values def _get_calling_var_args(evaluator, var_args): old_var_args = None while var_args != old_var_args: old_var_args = var_args for argument in reversed(var_args): if not isinstance(argument, pr.Statement): continue exp_list = argument.expression_list() if len(exp_list) != 2 or exp_list[0] not in ('*', '**'): continue names = evaluator.goto(argument, [exp_list[1].get_code()]) if len(names) != 1: break param = names[0].get_definition() if not isinstance(param, ExecutedParam): if isinstance(param, pr.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, pr.Array): var_args = param.var_args return var_args def get_params(evaluator, func, var_args): result = [] param_dict = {} for param in func.params: param_dict[str(param.get_name())] = param # There may be calls, which don't fit all the params, this just ignores it. #unpacked_va = _unpack_var_args(evaluator, var_args, func) unpacked_va = list(var_args.unpack()) var_arg_iterator = common.PushBackIterator(iter(unpacked_va)) non_matching_keys = defaultdict(lambda: []) keys_used = set() 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. key, va_values = next(var_arg_iterator, (None, ())) while key: keys_only = True k = unicode(key) try: key_param = param_dict[unicode(key)] except KeyError: non_matching_keys[key] += va_values else: result.append(_gen_param_name_copy(evaluator, func, var_args, key_param, values=[va_values])) 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: keys_used.add(k) key, va_values = next(var_arg_iterator, (None, ())) keys = [] values = [] array_type = None has_default_value = False if param.stars == 1: # *args param array_type = pr.Array.TUPLE lst_values = [va_values] if va_values else [] for key, va_values in var_arg_iterator: # Iterate until a key argument is found. if key: var_arg_iterator.push_back((key, va_values)) break lst_values.append(va_values) if lst_values: values = [iterable.FakeSequence(evaluator, tuple(lst_values), pr.Array.TUPLE)] #values = [helpers.stmts_to_stmt(v) for v in lst_values] elif param.stars == 2: # **kwargs param array_type = pr.Array.DICT values = [iterable.FakeDict(evaluator, dict(non_matching_keys))] non_matching_keys = {} else: # normal param if va_values is not None: values = va_values else: if param.default is not None: # No value: Return the default values. values = [param.default] else: # No value: Return an empty container values = [] if not keys_only and isinstance(var_args, pr.Array): 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) # Now add to result if it's not one of the previously covered cases. if not has_default_value and (not keys_only or param.stars == 2): keys_used.add(unicode(param.get_name())) result.append(_gen_param_name_copy(evaluator, func, var_args, param, keys=keys, values=values, array_type=array_type)) 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) - keys_used: param = param_dict[k] values = [] if param.default is None else [param.default] result.append(_gen_param_name_copy(evaluator, func, var_args, param, values)) 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, va_values in non_matching_keys.items(): m = "TypeError: %s() got an unexpected keyword argument '%s'." \ % (func.name, key) for value in va_values: analysis.add(evaluator, 'type-error-keyword-argument', value, message=m) remaining_params = list(var_arg_iterator) if remaining_params: m = _error_argument_count(func, len(unpacked_va)) for p in remaining_params[0][1]: analysis.add(evaluator, 'type-error-too-many-arguments', p, message=m) return result def _unpack_var_args(evaluator, var_args, func): """ Yields a key/value pair, the key is None, if its not a named arg. """ argument_list = [] from jedi.evaluate.representation import InstanceElement if isinstance(func, InstanceElement): # Include self at this place. argument_list.append((None, [helpers.FakeStatement([func.instance])])) print(var_args) # `var_args` is typically an Array, and not a list. for stmt in _reorder_var_args(var_args.iterate()): print(stmt) if not isinstance(stmt, pr.Statement): if stmt is None: argument_list.append((None, [])) # TODO generate warning? continue old = stmt # generate a statement if it's not already one. stmt = helpers.FakeStatement([old]) expression_list = stmt.expression_list() if not len(expression_list): continue # *args if expression_list[0] == '*': arrays = evaluator.eval_expression_list(expression_list[1:]) iterators = [_iterate_star_args(evaluator, a, func) for a in arrays] for values in list(zip_longest(*iterators)): argument_list.append((None, [v for v in values if v is not None])) # **kwargs elif expression_list[0] == '**': dct = {} for array in evaluator.eval_expression_list(expression_list[1:]): # Merge multiple kwargs dictionaries, if used with dynamic # parameters. s = _star_star_dict(evaluator, array, expression_list[1:], func) for name, (key, value) in s.items(): try: dct[name][1].add(value) except KeyError: dct[name] = key, set([value]) for key, values in dct.values(): # merge **kwargs/*args also for dynamic parameters for i, p in enumerate(func.params): if str(p.get_name()) == str(key) and not p.stars: try: k, vs = argument_list[i] except IndexError: pass else: if k is None: # k would imply a named argument # Don't merge if they orginate at the same # place. -> type-error-multiple-values if [v.parent for v in values] != [v.parent for v in vs]: vs.extend(values) break else: # default is to merge argument_list.append((key, values)) # Normal arguments (including key arguments). else: if stmt.assignment_details: key_arr, op = stmt.assignment_details[0] # Filter error tokens key_arr = [x for x in key_arr if isinstance(x, pr.Call)] # named parameter if key_arr and isinstance(key_arr[0], pr.Call): argument_list.append((key_arr[0].name, [stmt])) else: argument_list.append((None, [stmt])) return argument_list def _iterate_star_args(evaluator, array, expression_list, func): from jedi.evaluate.representation import Instance if isinstance(array, iterable.Array): for field_stmt in array: # yield from plz! yield field_stmt elif isinstance(array, iterable.Generator): for field_stmt in array.iter_content(): yield helpers.FakeStatement([field_stmt]) elif isinstance(array, Instance) and array.name.get_code() == 'tuple': debug.warning('Ignored a tuple *args input %s' % array) else: if expression_list: m = "TypeError: %s() argument after * must be a sequence, not %s" \ % (func.name.get_code(), array) analysis.add(evaluator, 'type-error-star', expression_list[0], message=m) def _star_star_dict(evaluator, array, expression_list, 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.Array) and array.type == pr.Array.DICT: for key_node, values in array._items(): for key in evaluator.eval_element(key_node): if precedence.is_string(key): dct[key.obj] += values else: if expression_list: m = "TypeError: %s argument after ** must be a mapping, not %s" \ % (func.name.get_code(), array) analysis.add(evaluator, 'type-error-star-star', expression_list[0], message=m) return dict(dct) def _gen_param_name_copy(evaluator, func, var_args, param, keys=(), values=(), array_type=None): """ Create a param with the original scope (of varargs) as parent. """ if isinstance(var_args, pr.Array): parent = var_args.parent start_pos = var_args.start_pos else: parent = func start_pos = 0, 0 # create an Array (-> needed for *args/**kwargs tuples/dicts) arr = iterable.FakeSequence(evaluator, tuple(values), array_type) # TODO change?! """ arr = pr.Array(helpers.FakeSubModule, start_pos, array_type, parent) key_stmts = [] for key in keys: key_stmts.append(helpers.FakeStatement([key], start_pos)) arr.keys = key_stmts arr.type = array_type """ new_param = ExecutedParam.from_param(values, param, parent, var_args) name = copy.copy(param.get_name()) name.parent = new_param return name 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))