[Python-checkins] r66438 - in sandbox/trunk/2to3: README example.py lib2to3/fixes/fix_metaclass.py lib2to3/tests/test_fixers.py

benjamin.peterson python-checkins at python.org
Sat Sep 13 04:32:31 CEST 2008


Author: benjamin.peterson
Date: Sat Sep 13 04:32:30 2008
New Revision: 66438

Log:
add Jack Diederich's fixer for metaclass syntax #2366

my contribution to this was adding a few tests and fixing a few bugs
I also reviewed it (Jack is a committer)


Added:
   sandbox/trunk/2to3/lib2to3/fixes/fix_metaclass.py   (contents, props changed)
Modified:
   sandbox/trunk/2to3/README
   sandbox/trunk/2to3/example.py
   sandbox/trunk/2to3/lib2to3/tests/test_fixers.py

Modified: sandbox/trunk/2to3/README
==============================================================================
--- sandbox/trunk/2to3/README	(original)
+++ sandbox/trunk/2to3/README	Sat Sep 13 04:32:30 2008
@@ -120,6 +120,8 @@
 * **fix_xreadlines** - "for x in f.xreadlines():" -> "for x in f:". Also,
   "g(f.xreadlines)" -> "g(f.__iter__)".
 
+* **fix_metaclass** - move __metaclass__ = M to class X(metaclass=M)
+
 
 Limitations
 ===========

Modified: sandbox/trunk/2to3/example.py
==============================================================================
--- sandbox/trunk/2to3/example.py	(original)
+++ sandbox/trunk/2to3/example.py	Sat Sep 13 04:32:30 2008
@@ -117,6 +117,18 @@
     apply(f, args=args, kwds=kwds)
     apply(f, args, kwds=kwds)
 
+def metaclass_examples():
+    class X:
+        __metaclass__ = Meta
+
+    class X(b1, b2):
+        bar = 23 # Comment on me!
+        __metaclass__ = Meta
+        spam = 27.23 # Laughable
+
+    class X:
+        __metaclass__ = Meta; x = 23; y = 34 # Yes, I can handle this, too.
+
 def intern_examples():
     #
     # These should be refactored:

Added: sandbox/trunk/2to3/lib2to3/fixes/fix_metaclass.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/2to3/lib2to3/fixes/fix_metaclass.py	Sat Sep 13 04:32:30 2008
@@ -0,0 +1,227 @@
+"""Fixer for __metaclass__ = X -> (metaclass=X) methods.
+
+   The various forms of classef (inherits nothing, inherits once, inherints
+   many) don't parse the same in the CST so we look at ALL classes for
+   a __metaclass__ and if we find one normalize the inherits to all be
+   an arglist.
+
+   For one-liner classes ('class X: pass') there is no indent/dedent so
+   we normalize those into having a suite.
+
+   Moving the __metaclass__ into the classdef can also cause the class
+   body to be empty so there is some special casing for that as well.
+
+   This fixer also tries very hard to keep original indenting and spacing
+   in all those corner cases.
+
+"""
+# Author: Jack Diederich
+
+import os
+
+# Local imports
+from .. import fixer_base
+from ..pygram import token
+from ..fixer_util import Name, syms, Node, Leaf
+
+
+def has_metaclass(parent):
+    """ we have to check the cls_node without changing it.
+        There are two possiblities:
+          1)  clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta')
+          2)  clsdef => simple_stmt => expr_stmt => Leaf('__meta')
+    """
+    for node in parent.children:
+        if node.type == syms.suite:
+            return has_metaclass(node)
+        elif node.type == syms.simple_stmt and node.children:
+            expr_node = node.children[0]
+            if expr_node.type == syms.expr_stmt and expr_node.children:
+                leaf_node = expr_node.children[0]
+                if leaf_node.value == '__metaclass__':
+                    return True
+    return False
+
+
+def fixup_parse_tree(cls_node):
+    """ one-line classes don't get a suite in the parse tree so we add
+        one to normalize the tree
+    """
+    for node in cls_node.children:
+        if node.type == syms.suite:
+            # already in the prefered format, do nothing
+            return
+
+    # !%@#! oneliners have no suite node, we have to fake one up
+    for i, node in enumerate(cls_node.children):
+        if node.type == token.COLON:
+            break
+    else:
+        raise ValueError("No class suite and no ':'!")
+
+    # move everything into a suite node
+    suite = Node(syms.suite, [])
+    while cls_node.children[i+1:]:
+        move_node = cls_node.children[i+1]
+        suite.append_child(move_node.clone())
+        move_node.remove()
+    cls_node.append_child(suite)
+    node = suite
+
+
+def fixup_simple_stmt(parent, i, stmt_node):
+    """ if there is a semi-colon all the parts count as part of the same
+        simple_stmt.  We just want the __metaclass__ part so we move
+        everything efter the semi-colon into its own simple_stmt node
+    """
+    for semi_ind, node in enumerate(stmt_node.children):
+        if node.type == token.SEMI: # *sigh*
+            break
+    else:
+        return
+
+    node.remove() # kill the semicolon
+    new_expr = Node(syms.expr_stmt, [])
+    new_stmt = Node(syms.simple_stmt, [new_expr])
+    while stmt_node.children[semi_ind:]:
+        move_node = stmt_node.children[semi_ind]
+        new_expr.append_child(move_node.clone())
+        move_node.remove()
+    parent.insert_child(i, new_stmt)
+    new_leaf1 = new_stmt.children[0].children[0]
+    old_leaf1 = stmt_node.children[0].children[0]
+    new_leaf1.set_prefix(old_leaf1.get_prefix())
+
+
+def remove_trailing_newline(node):
+    if node.children and node.children[-1].type == token.NEWLINE:
+        node.children[-1].remove()
+
+
+def find_metas(cls_node):
+    # find the suite node (Mmm, sweet nodes)
+    for node in cls_node.children:
+        if node.type == syms.suite:
+            break
+    else:
+        raise ValueError("No class suite!")
+
+    # look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ]
+    for i, simple_node in list(enumerate(node.children)):
+        if simple_node.type == syms.simple_stmt and simple_node.children:
+            expr_node = simple_node.children[0]
+            if expr_node.type == syms.expr_stmt and expr_node.children:
+                leaf_node = expr_node.children[0]
+                if leaf_node.value == '__metaclass__':
+                    fixup_simple_stmt(node, i, simple_node)
+                    remove_trailing_newline(simple_node)
+                    yield (node, i, simple_node)
+
+
+def fixup_indent(suite):
+    """ If an INDENT is followed by a thing with a prefix then nuke the prefix
+        Otherwise we get in trouble when removing __metaclass__ at suite start
+    """
+    kids = suite.children[::-1]
+    # find the first indent
+    while kids:
+        node = kids.pop()
+        if node.type == token.INDENT:
+            break
+
+    # find the first Leaf
+    while kids:
+        node = kids.pop()
+        if isinstance(node, Leaf) and node.type != token.DEDENT:
+            if node.prefix:
+                node.set_prefix('')
+            return
+        else:
+            kids.extend(node.children[::-1])
+
+
+class FixMetaclass(fixer_base.BaseFix):
+
+    PATTERN = """
+    classdef<any*>
+    """
+
+    def transform(self, node, results):
+        if not has_metaclass(node):
+            return node
+
+        fixup_parse_tree(node)
+
+        # find metaclasses, keep the last one
+        last_metaclass = None
+        for suite, i, stmt in find_metas(node):
+            last_metaclass = stmt
+            stmt.remove()
+
+        text_type = node.children[0].type # always Leaf(nnn, 'class')
+
+        # figure out what kind of classdef we have
+        if len(node.children) == 7:
+            # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite])
+            #                 0        1       2    3        4    5    6
+            if node.children[3].type == syms.arglist:
+                arglist = node.children[3]
+            # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite])
+            elif isinstance(node.children[3], Leaf):
+                parent = node.children[3].clone()
+                arglist = Node(syms.arglist, [parent])
+                node.set_child(3, arglist)
+            else:
+                raise ValueError("Unexpected class inheritance arglist")
+        elif len(node.children) == 6:
+            # Node(classdef, ['class', 'name', '(',  ')', ':', suite])
+            #                 0        1       2     3    4    5
+            arglist = Node(syms.arglist, [])
+            node.insert_child(3, arglist)
+        elif len(node.children) == 4:
+            # Node(classdef, ['class', 'name', ':', suite])
+            #                 0        1       2    3
+            arglist = Node(syms.arglist, [])
+            node.insert_child(2, Leaf(token.RPAR, ')'))
+            node.insert_child(2, arglist)
+            node.insert_child(2, Leaf(token.LPAR, '('))
+        else:
+            raise ValueError("Unexpected class definition")
+
+        # now stick the metaclass in the arglist
+        meta_txt = last_metaclass.children[0].children[0]
+        meta_txt.value = 'metaclass'
+        orig_meta_prefix = meta_txt.get_prefix()
+
+        if arglist.children:
+            arglist.append_child(Leaf(token.COMMA, ','))
+            meta_txt.set_prefix(' ')
+        else:
+            meta_txt.set_prefix('')
+
+        # compact the expression "metaclass = Meta" -> "metaclass=Meta"
+        expr_stmt = last_metaclass.children[0]
+        assert expr_stmt.type == syms.expr_stmt
+        expr_stmt.children[1].set_prefix('')
+        expr_stmt.children[2].set_prefix('')
+
+        arglist.append_child(last_metaclass)
+
+        fixup_indent(suite)
+
+        # check for empty suite
+        if not suite.children:
+            # one-liner that was just __metaclass_
+            suite.remove()
+            pass_leaf = Leaf(text_type, 'pass')
+            pass_leaf.set_prefix(orig_meta_prefix)
+            node.append_child(pass_leaf)
+            node.append_child(Leaf(token.NEWLINE, os.linesep))
+
+        elif len(suite.children) > 1 and \
+                 (suite.children[-2].type == token.INDENT and
+                  suite.children[-1].type == token.DEDENT):
+            # there was only one line in the class body and it was __metaclass__
+            pass_leaf = Leaf(text_type, 'pass')
+            suite.insert_child(-1, pass_leaf)
+            suite.insert_child(-1, Leaf(token.NEWLINE, os.linesep))

Modified: sandbox/trunk/2to3/lib2to3/tests/test_fixers.py
==============================================================================
--- sandbox/trunk/2to3/lib2to3/tests/test_fixers.py	(original)
+++ sandbox/trunk/2to3/lib2to3/tests/test_fixers.py	Sat Sep 13 04:32:30 2008
@@ -3438,6 +3438,133 @@
         s = """[i for i in m]"""
         self.unchanged(s)
 
+class Test_metaclass(FixerTestCase):
+
+    fixer = 'metaclass'
+
+    def test_unchanged(self):
+        self.unchanged("class X(): pass")
+        self.unchanged("class X(object): pass")
+        self.unchanged("class X(object1, object2): pass")
+        self.unchanged("class X(object1, object2, object3): pass")
+        self.unchanged("class X(metaclass=Meta): pass")
+        self.unchanged("class X(b, arg=23, metclass=Meta): pass")
+        self.unchanged("class X(b, arg=23, metaclass=Meta, other=42): pass")
+
+        s = """
+        class X:
+            def __metaclass__(self): pass
+        """
+        self.unchanged(s)
+
+    def test_comments(self):
+        b = """
+        class X:
+            # hi
+            __metaclass__ = AppleMeta
+        """
+        a = """
+        class X(metaclass=AppleMeta):
+            # hi
+            pass
+        """
+        self.check(b, a)
+
+        b = """
+        class X:
+            __metaclass__ = Meta
+            # Bedtime!
+        """
+        a = """
+        class X(metaclass=Meta):
+            pass
+            # Bedtime!
+        """
+        self.check(b, a)
+
+    def test_meta(self):
+        # no-parent class, odd body
+        b = """
+        class X():
+            __metaclass__ = Q
+            pass
+        """
+        a = """
+        class X(metaclass=Q):
+            pass
+        """
+        self.check(b, a)
+
+        # one parent class, no body
+        b = """class X(object): __metaclass__ = Q"""
+        a = """class X(object, metaclass=Q): pass"""
+        self.check(b, a)
+
+
+        # one parent, simple body
+        b = """
+        class X(object):
+            __metaclass__ = Meta
+            bar = 7
+        """
+        a = """
+        class X(object, metaclass=Meta):
+            bar = 7
+        """
+        self.check(b, a)
+
+        b = """
+        class X:
+            __metaclass__ = Meta; x = 4; g = 23
+        """
+        a = """
+        class X(metaclass=Meta):
+            x = 4; g = 23
+        """
+        self.check(b, a)
+
+        # one parent, simple body, __metaclass__ last
+        b = """
+        class X(object):
+            bar = 7
+            __metaclass__ = Meta
+        """
+        a = """
+        class X(object, metaclass=Meta):
+            bar = 7
+        """
+        self.check(b, a)
+
+        # redefining __metaclass__
+        b = """
+        class X():
+            __metaclass__ = A
+            __metaclass__ = B
+            bar = 7
+        """
+        a = """
+        class X(metaclass=B):
+            bar = 7
+        """
+        self.check(b, a)
+
+        # multiple inheritance, simple body
+        b = """
+        class X(clsA, clsB):
+            __metaclass__ = Meta
+            bar = 7
+        """
+        a = """
+        class X(clsA, clsB, metaclass=Meta):
+            bar = 7
+        """
+        self.check(b, a)
+
+        # keywords in the class statement
+        b = """class m(a, arg=23): __metaclass__ = Meta"""
+        a = """class m(a, arg=23, metaclass=Meta): pass"""
+        self.check(b, a)
+
 
 if __name__ == "__main__":
     import __main__


More information about the Python-checkins mailing list