17db96d56Sopenharmony_ci"""Fixer for function definitions with tuple parameters.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_cidef func(((a, b), c), d):
47db96d56Sopenharmony_ci    ...
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ci    ->
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_cidef func(x, d):
97db96d56Sopenharmony_ci    ((a, b), c) = x
107db96d56Sopenharmony_ci    ...
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciIt will also support lambdas:
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci    lambda (x, y): x + y -> lambda t: t[0] + t[1]
157db96d56Sopenharmony_ci
167db96d56Sopenharmony_ci    # The parens are a syntax error in Python 3
177db96d56Sopenharmony_ci    lambda (x): x + y -> lambda x: x + y
187db96d56Sopenharmony_ci"""
197db96d56Sopenharmony_ci# Author: Collin Winter
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci# Local imports
227db96d56Sopenharmony_cifrom .. import pytree
237db96d56Sopenharmony_cifrom ..pgen2 import token
247db96d56Sopenharmony_cifrom .. import fixer_base
257db96d56Sopenharmony_cifrom ..fixer_util import Assign, Name, Newline, Number, Subscript, syms
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_cidef is_docstring(stmt):
287db96d56Sopenharmony_ci    return isinstance(stmt, pytree.Node) and \
297db96d56Sopenharmony_ci           stmt.children[0].type == token.STRING
307db96d56Sopenharmony_ci
317db96d56Sopenharmony_ciclass FixTupleParams(fixer_base.BaseFix):
327db96d56Sopenharmony_ci    run_order = 4 #use a lower order since lambda is part of other
337db96d56Sopenharmony_ci                  #patterns
347db96d56Sopenharmony_ci    BM_compatible = True
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ci    PATTERN = """
377db96d56Sopenharmony_ci              funcdef< 'def' any parameters< '(' args=any ')' >
387db96d56Sopenharmony_ci                       ['->' any] ':' suite=any+ >
397db96d56Sopenharmony_ci              |
407db96d56Sopenharmony_ci              lambda=
417db96d56Sopenharmony_ci              lambdef< 'lambda' args=vfpdef< '(' inner=any ')' >
427db96d56Sopenharmony_ci                       ':' body=any
437db96d56Sopenharmony_ci              >
447db96d56Sopenharmony_ci              """
457db96d56Sopenharmony_ci
467db96d56Sopenharmony_ci    def transform(self, node, results):
477db96d56Sopenharmony_ci        if "lambda" in results:
487db96d56Sopenharmony_ci            return self.transform_lambda(node, results)
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci        new_lines = []
517db96d56Sopenharmony_ci        suite = results["suite"]
527db96d56Sopenharmony_ci        args = results["args"]
537db96d56Sopenharmony_ci        # This crap is so "def foo(...): x = 5; y = 7" is handled correctly.
547db96d56Sopenharmony_ci        # TODO(cwinter): suite-cleanup
557db96d56Sopenharmony_ci        if suite[0].children[1].type == token.INDENT:
567db96d56Sopenharmony_ci            start = 2
577db96d56Sopenharmony_ci            indent = suite[0].children[1].value
587db96d56Sopenharmony_ci            end = Newline()
597db96d56Sopenharmony_ci        else:
607db96d56Sopenharmony_ci            start = 0
617db96d56Sopenharmony_ci            indent = "; "
627db96d56Sopenharmony_ci            end = pytree.Leaf(token.INDENT, "")
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci        # We need access to self for new_name(), and making this a method
657db96d56Sopenharmony_ci        #  doesn't feel right. Closing over self and new_lines makes the
667db96d56Sopenharmony_ci        #  code below cleaner.
677db96d56Sopenharmony_ci        def handle_tuple(tuple_arg, add_prefix=False):
687db96d56Sopenharmony_ci            n = Name(self.new_name())
697db96d56Sopenharmony_ci            arg = tuple_arg.clone()
707db96d56Sopenharmony_ci            arg.prefix = ""
717db96d56Sopenharmony_ci            stmt = Assign(arg, n.clone())
727db96d56Sopenharmony_ci            if add_prefix:
737db96d56Sopenharmony_ci                n.prefix = " "
747db96d56Sopenharmony_ci            tuple_arg.replace(n)
757db96d56Sopenharmony_ci            new_lines.append(pytree.Node(syms.simple_stmt,
767db96d56Sopenharmony_ci                                         [stmt, end.clone()]))
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci        if args.type == syms.tfpdef:
797db96d56Sopenharmony_ci            handle_tuple(args)
807db96d56Sopenharmony_ci        elif args.type == syms.typedargslist:
817db96d56Sopenharmony_ci            for i, arg in enumerate(args.children):
827db96d56Sopenharmony_ci                if arg.type == syms.tfpdef:
837db96d56Sopenharmony_ci                    # Without add_prefix, the emitted code is correct,
847db96d56Sopenharmony_ci                    #  just ugly.
857db96d56Sopenharmony_ci                    handle_tuple(arg, add_prefix=(i > 0))
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci        if not new_lines:
887db96d56Sopenharmony_ci            return
897db96d56Sopenharmony_ci
907db96d56Sopenharmony_ci        # This isn't strictly necessary, but it plays nicely with other fixers.
917db96d56Sopenharmony_ci        # TODO(cwinter) get rid of this when children becomes a smart list
927db96d56Sopenharmony_ci        for line in new_lines:
937db96d56Sopenharmony_ci            line.parent = suite[0]
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_ci        # TODO(cwinter) suite-cleanup
967db96d56Sopenharmony_ci        after = start
977db96d56Sopenharmony_ci        if start == 0:
987db96d56Sopenharmony_ci            new_lines[0].prefix = " "
997db96d56Sopenharmony_ci        elif is_docstring(suite[0].children[start]):
1007db96d56Sopenharmony_ci            new_lines[0].prefix = indent
1017db96d56Sopenharmony_ci            after = start + 1
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci        for line in new_lines:
1047db96d56Sopenharmony_ci            line.parent = suite[0]
1057db96d56Sopenharmony_ci        suite[0].children[after:after] = new_lines
1067db96d56Sopenharmony_ci        for i in range(after+1, after+len(new_lines)+1):
1077db96d56Sopenharmony_ci            suite[0].children[i].prefix = indent
1087db96d56Sopenharmony_ci        suite[0].changed()
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_ci    def transform_lambda(self, node, results):
1117db96d56Sopenharmony_ci        args = results["args"]
1127db96d56Sopenharmony_ci        body = results["body"]
1137db96d56Sopenharmony_ci        inner = simplify_args(results["inner"])
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ci        # Replace lambda ((((x)))): x  with lambda x: x
1167db96d56Sopenharmony_ci        if inner.type == token.NAME:
1177db96d56Sopenharmony_ci            inner = inner.clone()
1187db96d56Sopenharmony_ci            inner.prefix = " "
1197db96d56Sopenharmony_ci            args.replace(inner)
1207db96d56Sopenharmony_ci            return
1217db96d56Sopenharmony_ci
1227db96d56Sopenharmony_ci        params = find_params(args)
1237db96d56Sopenharmony_ci        to_index = map_to_index(params)
1247db96d56Sopenharmony_ci        tup_name = self.new_name(tuple_name(params))
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci        new_param = Name(tup_name, prefix=" ")
1277db96d56Sopenharmony_ci        args.replace(new_param.clone())
1287db96d56Sopenharmony_ci        for n in body.post_order():
1297db96d56Sopenharmony_ci            if n.type == token.NAME and n.value in to_index:
1307db96d56Sopenharmony_ci                subscripts = [c.clone() for c in to_index[n.value]]
1317db96d56Sopenharmony_ci                new = pytree.Node(syms.power,
1327db96d56Sopenharmony_ci                                  [new_param.clone()] + subscripts)
1337db96d56Sopenharmony_ci                new.prefix = n.prefix
1347db96d56Sopenharmony_ci                n.replace(new)
1357db96d56Sopenharmony_ci
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci### Helper functions for transform_lambda()
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_cidef simplify_args(node):
1407db96d56Sopenharmony_ci    if node.type in (syms.vfplist, token.NAME):
1417db96d56Sopenharmony_ci        return node
1427db96d56Sopenharmony_ci    elif node.type == syms.vfpdef:
1437db96d56Sopenharmony_ci        # These look like vfpdef< '(' x ')' > where x is NAME
1447db96d56Sopenharmony_ci        # or another vfpdef instance (leading to recursion).
1457db96d56Sopenharmony_ci        while node.type == syms.vfpdef:
1467db96d56Sopenharmony_ci            node = node.children[1]
1477db96d56Sopenharmony_ci        return node
1487db96d56Sopenharmony_ci    raise RuntimeError("Received unexpected node %s" % node)
1497db96d56Sopenharmony_ci
1507db96d56Sopenharmony_cidef find_params(node):
1517db96d56Sopenharmony_ci    if node.type == syms.vfpdef:
1527db96d56Sopenharmony_ci        return find_params(node.children[1])
1537db96d56Sopenharmony_ci    elif node.type == token.NAME:
1547db96d56Sopenharmony_ci        return node.value
1557db96d56Sopenharmony_ci    return [find_params(c) for c in node.children if c.type != token.COMMA]
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_cidef map_to_index(param_list, prefix=[], d=None):
1587db96d56Sopenharmony_ci    if d is None:
1597db96d56Sopenharmony_ci        d = {}
1607db96d56Sopenharmony_ci    for i, obj in enumerate(param_list):
1617db96d56Sopenharmony_ci        trailer = [Subscript(Number(str(i)))]
1627db96d56Sopenharmony_ci        if isinstance(obj, list):
1637db96d56Sopenharmony_ci            map_to_index(obj, trailer, d=d)
1647db96d56Sopenharmony_ci        else:
1657db96d56Sopenharmony_ci            d[obj] = prefix + trailer
1667db96d56Sopenharmony_ci    return d
1677db96d56Sopenharmony_ci
1687db96d56Sopenharmony_cidef tuple_name(param_list):
1697db96d56Sopenharmony_ci    l = []
1707db96d56Sopenharmony_ci    for obj in param_list:
1717db96d56Sopenharmony_ci        if isinstance(obj, list):
1727db96d56Sopenharmony_ci            l.append(tuple_name(obj))
1737db96d56Sopenharmony_ci        else:
1747db96d56Sopenharmony_ci            l.append(obj)
1757db96d56Sopenharmony_ci    return "_".join(l)
176