Home | History | Annotate | Download | only in fixes
      1 # Copyright 2007 Google, Inc. All Rights Reserved.
      2 # Licensed to PSF under a Contributor Agreement.
      3 
      4 """Fixer for dict methods.
      5 
      6 d.keys() -> list(d.keys())
      7 d.items() -> list(d.items())
      8 d.values() -> list(d.values())
      9 
     10 d.iterkeys() -> iter(d.keys())
     11 d.iteritems() -> iter(d.items())
     12 d.itervalues() -> iter(d.values())
     13 
     14 d.viewkeys() -> d.keys()
     15 d.viewitems() -> d.items()
     16 d.viewvalues() -> d.values()
     17 
     18 Except in certain very specific contexts: the iter() can be dropped
     19 when the context is list(), sorted(), iter() or for...in; the list()
     20 can be dropped when the context is list() or sorted() (but not iter()
     21 or for...in!). Special contexts that apply to both: list(), sorted(), tuple()
     22 set(), any(), all(), sum().
     23 
     24 Note: iter(d.keys()) could be written as iter(d) but since the
     25 original d.iterkeys() was also redundant we don't fix this.  And there
     26 are (rare) contexts where it makes a difference (e.g. when passing it
     27 as an argument to a function that introspects the argument).
     28 """
     29 
     30 # Local imports
     31 from .. import pytree
     32 from .. import patcomp
     33 from ..pgen2 import token
     34 from .. import fixer_base
     35 from ..fixer_util import Name, Call, LParen, RParen, ArgList, Dot
     36 from .. import fixer_util
     37 
     38 
     39 iter_exempt = fixer_util.consuming_calls | set(["iter"])
     40 
     41 
     42 class FixDict(fixer_base.BaseFix):
     43     BM_compatible = True
     44 
     45     PATTERN = """
     46     power< head=any+
     47          trailer< '.' method=('keys'|'items'|'values'|
     48                               'iterkeys'|'iteritems'|'itervalues'|
     49                               'viewkeys'|'viewitems'|'viewvalues') >
     50          parens=trailer< '(' ')' >
     51          tail=any*
     52     >
     53     """
     54 
     55     def transform(self, node, results):
     56         head = results["head"]
     57         method = results["method"][0] # Extract node for method name
     58         tail = results["tail"]
     59         syms = self.syms
     60         method_name = method.value
     61         isiter = method_name.startswith(u"iter")
     62         isview = method_name.startswith(u"view")
     63         if isiter or isview:
     64             method_name = method_name[4:]
     65         assert method_name in (u"keys", u"items", u"values"), repr(method)
     66         head = [n.clone() for n in head]
     67         tail = [n.clone() for n in tail]
     68         special = not tail and self.in_special_context(node, isiter)
     69         args = head + [pytree.Node(syms.trailer,
     70                                    [Dot(),
     71                                     Name(method_name,
     72                                          prefix=method.prefix)]),
     73                        results["parens"].clone()]
     74         new = pytree.Node(syms.power, args)
     75         if not (special or isview):
     76             new.prefix = u""
     77             new = Call(Name(u"iter" if isiter else u"list"), [new])
     78         if tail:
     79             new = pytree.Node(syms.power, [new] + tail)
     80         new.prefix = node.prefix
     81         return new
     82 
     83     P1 = "power< func=NAME trailer< '(' node=any ')' > any* >"
     84     p1 = patcomp.compile_pattern(P1)
     85 
     86     P2 = """for_stmt< 'for' any 'in' node=any ':' any* >
     87             | comp_for< 'for' any 'in' node=any any* >
     88          """
     89     p2 = patcomp.compile_pattern(P2)
     90 
     91     def in_special_context(self, node, isiter):
     92         if node.parent is None:
     93             return False
     94         results = {}
     95         if (node.parent.parent is not None and
     96                self.p1.match(node.parent.parent, results) and
     97                results["node"] is node):
     98             if isiter:
     99                 # iter(d.iterkeys()) -> iter(d.keys()), etc.
    100                 return results["func"].value in iter_exempt
    101             else:
    102                 # list(d.keys()) -> list(d.keys()), etc.
    103                 return results["func"].value in fixer_util.consuming_calls
    104         if not isiter:
    105             return False
    106         # for ... in d.iterkeys() -> for ... in d.keys(), etc.
    107         return self.p2.match(node.parent, results) and results["node"] is node
    108