diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 474b43dc..041274c2 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -710,23 +710,44 @@ class Class(ClassOrFunc): def _create_params(parent, argslist_list): """ - TODO DOC - This is a function to hack the general parser structure. - Preparing the replacement of *argslist with a list of Params. + `argslist_list` is a list that can contain an argslist as a first item, but + most not. It's basically the items between the parameter brackets (which is + at most one item). + This function modifies the parser structure. It generates `Param` objects + from the normal ast. Those param objects do not exist in a normal ast, but + make the evaluation of the ast tree so much easier. + You could also say that this function replaces the argslist node with a + list of Param objects. """ - if not argslist_list: + def check_python2_nested_param(node): + """ + Python 2 allows params to look like ``def x(a, (b, c))``, which is + basically a way of unpacking tuples in params. Python 3 has ditched + this behavior. Jedi currently just ignores those constructs. + """ + return node.type == 'tfpdef' and node.children[0] == '(' + + try: + first = argslist_list[0] + except IndexError: return [] - if argslist_list[0].type == 'name': - return [Param([argslist_list[0]], parent)] + if first.type in ('name', 'tfpdef'): + if check_python2_nested_param(first): + return [] + else: + return [Param([first], parent)] else: # argslist is a `typedargslist` or a `varargslist`. - children = argslist_list[0].children + children = first.children params = [] start = 0 # Start with offset 1, because the end is higher. for end, child in enumerate(children + [None], 1): if child is None or child == ',': - params.append(Param(children[start:end], parent)) + new_children = children[start:end] + if check_python2_nested_param(new_children[0]): + continue + params.append(Param(new_children, parent)) start = end return params diff --git a/test/test_parser/test_parser.py b/test/test_parser/test_parser.py index 3a55860c..595f124a 100644 --- a/test/test_parser/test_parser.py +++ b/test/test_parser/test_parser.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import sys import jedi from jedi._compatibility import u, is_py3 @@ -180,3 +181,24 @@ def test_end_pos_error_correction(): # at all. We just want to make sure that the module end_pos is correct! assert func.end_pos == (3, 0) assert m.end_pos == (2, 2) + + +def test_param_splitting(): + """ + Jedi splits parameters into params, this is not what the grammar does, + but Jedi does this to simplify argument parsing. + """ + def check(src, result): + # Python 2 tuple params should be ignored for now. + grammar = load_grammar('grammar%s.%s' % sys.version_info[:2]) + m = Parser(grammar, u(src)).module + if is_py3: + assert not m.subscopes + else: + # We don't want b and c to be a part of the param enumeration. Just + # ignore them, because it's not what we want to support in the + # future. + assert [str(param.name) for param in m.subscopes[0].params] == result + + check('def x(a, (b, c)):\n pass', ['a']) + check('def x((b, c)):\n pass', [])