import copy from jedi._compatibility import unicode, zip_longest from jedi.parser import representation as pr from jedi.evaluate import iterable from jedi import common from jedi.evaluate import helpers from jedi.evaluate import analysis from jedi.evaluate.compiled import CompiledObject class ExecutedParam(pr.Param): def __init__(self): """Don't use this method, it's just here to overwrite the old one.""" pass @classmethod def from_param(cls, param, parent, var_args): instance = cls() 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 _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].parent 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) var_arg_iterator = common.PushBackIterator(iter(unpacked_va)) non_matching_keys = [] keys_used = set() keys_only = False va_values = None 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.append((key, va_values)) else: result.append(_gen_param_name_copy(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] 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[0]: values = [helpers.stmts_to_stmt(v) for v in lst_values] elif param.stars == 2: # **kwargs param array_type = pr.Array.DICT if non_matching_keys: keys, values = zip(*non_matching_keys) values = [helpers.stmts_to_stmt(list(v)) for v in values] non_matching_keys = [] else: # normal param if va_values: values = va_values else: if param.assignment_details: # No value: Return the default values. has_default_value = True result.append(param.get_name()) # TODO is this allowed? it changes it long time. param.is_generated = True 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(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] result.append(_gen_param_name_copy(func, var_args, param)) if not (non_matching_keys or had_multiple_value_error or param.stars or param.assignment_details): # 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: 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])])) # `var_args` is typically an Array, and not a list. for stmt in _reorder_var_args(var_args): 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, expression_list[1:], 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 _reorder_var_args(var_args): """ Reordering var_args is necessary, because star args sometimes appear after named argument, but in the actual order it's prepended. """ 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 _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 == 'tuple': pass else: if expression_list: m = "TypeError: %s() argument after * must be a sequence, not %s" \ % (func.name, array) analysis.add(evaluator, 'type-error-star', expression_list[0], message=m) def _star_star_dict(evaluator, array, expression_list, func): dct = {} from jedi.evaluate.representation import Instance if isinstance(array, Instance) and array.name == '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_stmt, value_stmt in array.items(): # first index, is the key if syntactically correct call = key_stmt.expression_list()[0] if isinstance(call, pr.Name): key = call elif isinstance(call, pr.Call): key = call.name else: continue # We ignore complicated statements here, for now. # If the string is a duplicate, we don't care it's illegal Python # anyway. dct[str(key)] = key, value_stmt else: if expression_list: m = "TypeError: %s argument after ** must be a mapping, not %s" \ % (func.name, array) analysis.add(evaluator, 'type-error-star-star', expression_list[0], message=m) return dct def _gen_param_name_copy(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 new_param = ExecutedParam.from_param(param, parent, var_args) # create an Array (-> needed for *args/**kwargs tuples/dicts) arr = pr.Array(helpers.FakeSubModule, start_pos, array_type, parent) arr.values = list(values) # Arrays only work with list. key_stmts = [] for key in keys: key_stmts.append(helpers.FakeStatement([key], start_pos)) arr.keys = key_stmts arr.type = array_type new_param.set_expression_list([arr]) 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.assignment_details 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))