From acb4959a6a792d03ba7d64b41a7156dd5bc1a680 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 4 Jun 2014 17:18:09 +0200 Subject: [PATCH] temporary very unfinished solution for the *args/**kwargs combination problem, if they are used in common with dynamic params. This doesn't solve the issue entirely, but it's at least a start and will probably solve all autocompletion issues. However, static analysis needs a little bit more than that. --- jedi/evaluate/finder.py | 2 +- jedi/evaluate/param.py | 33 +++++++++++++++++++------- test/static_analysis/star_arguments.py | 32 +++++++++++++++++++++++++ test/test_integration.py | 6 ++--- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 72bd7b6e..3fed2e26 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -54,7 +54,7 @@ class NameFinder(object): % (self._last_filter_name_scope, self.name_str)) analysis.add(self._evaluator, err_type, self.name_str, message) - debug.dbg('finder._names_to_types: %s, old: %s', names, types) + debug.dbg('finder._names_to_types: %s -> %s', names, types) return self._resolve_descriptors(types) def scopes(self, search_global=False): diff --git a/jedi/evaluate/param.py b/jedi/evaluate/param.py index 2bb9cb10..c290d441 100644 --- a/jedi/evaluate/param.py +++ b/jedi/evaluate/param.py @@ -75,7 +75,8 @@ def get_params(evaluator, func, var_args): 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. - var_arg_iterator = common.PushBackIterator(_var_args_iterator(evaluator, var_args)) + va = _unpack_var_args(evaluator, var_args, func.params) + var_arg_iterator = common.PushBackIterator(iter(va)) non_matching_keys = [] keys_used = set() @@ -125,12 +126,14 @@ def get_params(evaluator, func, var_args): break lst_values.append(va_values) if lst_values[0]: +#print('args', lst_values[0][0].parent.start_pos) 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) +# print('kw', values[0][0].parent.start_pos) values = [helpers.stmts_to_stmt(list(v)) for v in values] non_matching_keys = [] else: @@ -168,7 +171,8 @@ def get_params(evaluator, func, var_args): for k in set(param_dict) - keys_used: result.append(_gen_param_name_copy(func, var_args, param_dict[k])) - if not non_matching_keys and not had_multiple_value_error: + if not (non_matching_keys or had_multiple_value_error + or param_dict[k].stars): # 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: @@ -191,15 +195,17 @@ def get_params(evaluator, func, var_args): return result -def _var_args_iterator(evaluator, var_args): +def _unpack_var_args(evaluator, var_args, params): """ Yields a key/value pair, the key is None, if its not a named arg. """ + argument_list = [] # `var_args` is typically an Array, and not a list. for stmt in var_args: if not isinstance(stmt, pr.Statement): if stmt is None: - yield None, [] + argument_list.append((None, [])) + # TODO generate warning? continue old = stmt # generate a statement if it's not already one. @@ -213,7 +219,7 @@ def _var_args_iterator(evaluator, var_args): arrays = evaluator.eval_expression_list(expression_list[1:]) iterators = [_iterate_star_args(a) for a in arrays] for values in list(zip_longest(*iterators)): - yield None, [v for v in values if v is not None] + argument_list.append((None, [v for v in values if v is not None])) # **kwargs elif expression_list[0] == '**': dct = {} @@ -223,17 +229,28 @@ def _var_args_iterator(evaluator, var_args): dct[name][1].add(value) except KeyError: dct[name] = key, set([value]) + for key, values in dct.values(): - yield key, values + for i, p in enumerate(params): + if str(p.get_name()) == str(key) and not p.stars: + try: + if argument_list[i][0] is None: + argument_list[i][1].extend(values) + break + except IndexError: + pass + else: + argument_list.append((key, values)) # Normal arguments (including key arguments). else: if stmt.assignment_details: key_arr, op = stmt.assignment_details[0] # named parameter if key_arr and isinstance(key_arr[0], pr.Call): - yield key_arr[0].name, [stmt] + argument_list.append((key_arr[0].name, [stmt])) else: - yield None, [stmt] + argument_list.append((None, [stmt])) + return argument_list def _iterate_star_args(array): diff --git a/test/static_analysis/star_arguments.py b/test/static_analysis/star_arguments.py index 1009871d..a2acac85 100644 --- a/test/static_analysis/star_arguments.py +++ b/test/static_analysis/star_arguments.py @@ -61,3 +61,35 @@ kwargs_nested() kwargs_nested(c=2, d=4) #! 13 type-error-multiple-values kwargs_nested(c=2, a=4) + +# ----------------- +# mixed *args/**kwargs +# ----------------- + +def simple_mixed(a, b, c): + return b + + +def mixed(*args, **kwargs): + return simple_mixed(1, *args, **kwargs) + +mixed(1, 2) +mixed(1, c=2) +mixed(b=2, c=3) +mixed(c=4, b='') + +# need separate functions, otherwise these might swallow the errors +def mixed2(*args, **kwargs): + return simple_mixed(1, *args, **kwargs) + + +#! 6 type-error-too-few-arguments +mixed2(c=2) +#! 6 type-error-too-few-arguments +mixed2(3) +#! 13 type-error-too-many-arguments +mixed2(3, 4, 5) +#! 13 type-error-too-many-arguments +mixed2(3, 4, c=5) +#! 6 type-error-multiple-values +mixed2(3, b=5) diff --git a/test/test_integration.py b/test/test_integration.py index 237e823e..edf6ab62 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -26,12 +26,12 @@ def assert_static_analysis(case, actual, desired): d = set(desired) assert actual == desired, """ Test %r failed. -specified, not raised = %s -missing = %s +not raised = %s +unspecified = %s """ % (case, sorted(d - a), sorted(a - d)) -def test_integration(case, monkeypatch): +def test_completion(case, monkeypatch): if case.skip is not None: pytest.skip(case.skip) repo_root = helpers.root_dir