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