1# Copyright 2006 Google, Inc. All Rights Reserved.
2# Licensed to PSF under a Contributor Agreement.
3
4"""Fixer for has_key().
5
6Calls to .has_key() methods are expressed in terms of the 'in'
7operator:
8
9    d.has_key(k) -> k in d
10
11CAVEATS:
121) While the primary target of this fixer is dict.has_key(), the
13   fixer will change any has_key() method call, regardless of its
14   class.
15
162) Cases like this will not be converted:
17
18    m = d.has_key
19    if m(k):
20        ...
21
22   Only *calls* to has_key() are converted. While it is possible to
23   convert the above to something like
24
25    m = d.__contains__
26    if m(k):
27        ...
28
29   this is currently not done.
30"""
31
32# Local imports
33from .. import pytree
34from .. import fixer_base
35from ..fixer_util import Name, parenthesize
36
37
38class FixHasKey(fixer_base.BaseFix):
39    BM_compatible = True
40
41    PATTERN = """
42    anchor=power<
43        before=any+
44        trailer< '.' 'has_key' >
45        trailer<
46            '('
47            ( not(arglist | argument<any '=' any>) arg=any
48            | arglist<(not argument<any '=' any>) arg=any ','>
49            )
50            ')'
51        >
52        after=any*
53    >
54    |
55    negation=not_test<
56        'not'
57        anchor=power<
58            before=any+
59            trailer< '.' 'has_key' >
60            trailer<
61                '('
62                ( not(arglist | argument<any '=' any>) arg=any
63                | arglist<(not argument<any '=' any>) arg=any ','>
64                )
65                ')'
66            >
67        >
68    >
69    """
70
71    def transform(self, node, results):
72        assert results
73        syms = self.syms
74        if (node.parent.type == syms.not_test and
75            self.pattern.match(node.parent)):
76            # Don't transform a node matching the first alternative of the
77            # pattern when its parent matches the second alternative
78            return None
79        negation = results.get("negation")
80        anchor = results["anchor"]
81        prefix = node.prefix
82        before = [n.clone() for n in results["before"]]
83        arg = results["arg"].clone()
84        after = results.get("after")
85        if after:
86            after = [n.clone() for n in after]
87        if arg.type in (syms.comparison, syms.not_test, syms.and_test,
88                        syms.or_test, syms.test, syms.lambdef, syms.argument):
89            arg = parenthesize(arg)
90        if len(before) == 1:
91            before = before[0]
92        else:
93            before = pytree.Node(syms.power, before)
94        before.prefix = " "
95        n_op = Name("in", prefix=" ")
96        if negation:
97            n_not = Name("not", prefix=" ")
98            n_op = pytree.Node(syms.comp_op, (n_not, n_op))
99        new = pytree.Node(syms.comparison, (arg, n_op, before))
100        if after:
101            new = parenthesize(new)
102            new = pytree.Node(syms.power, (new,) + tuple(after))
103        if node.parent.type in (syms.comparison, syms.expr, syms.xor_expr,
104                                syms.and_expr, syms.shift_expr,
105                                syms.arith_expr, syms.term,
106                                syms.factor, syms.power):
107            new = parenthesize(new)
108        new.prefix = prefix
109        return new
110