[pypy-commit] pypy py3k: cpython issue1294232: fix certain cases of metaclass calculation

pjenvey noreply at buildbot.pypy.org
Mon Nov 19 22:28:27 CET 2012


Author: Philip Jenvey <pjenvey at underboss.org>
Branch: py3k
Changeset: r59001:c9fad5f8ea60
Date: 2012-11-19 13:29 -0800
http://bitbucket.org/pypy/pypy/changeset/c9fad5f8ea60/

Log:	cpython issue1294232: fix certain cases of metaclass calculation

diff --git a/pypy/module/__builtin__/compiling.py b/pypy/module/__builtin__/compiling.py
--- a/pypy/module/__builtin__/compiling.py
+++ b/pypy/module/__builtin__/compiling.py
@@ -125,11 +125,19 @@
     bases_w, kwds_w = __args__.unpack()
     w_bases = space.newtuple(bases_w)
     w_meta = kwds_w.pop('metaclass', None)
-    if w_meta is None:
+    if w_meta is not None:
+        isclass = space.isinstance_w(w_meta, space.w_type)
+    else:
         if bases_w:
             w_meta = space.type(bases_w[0])
         else:
             w_meta = space.w_type
+        isclass = True
+    if isclass:
+        # w_meta is really a class, so check for a more derived
+        # metaclass, or possible metaclass conflicts
+        from pypy.objspace.std.typetype import _calculate_metaclass
+        w_meta = _calculate_metaclass(space, w_meta, bases_w)
     
     try:
         w_prep = space.getattr(w_meta, space.wrap("__prepare__"))
diff --git a/pypy/objspace/std/test/test_typeobject.py b/pypy/objspace/std/test/test_typeobject.py
--- a/pypy/objspace/std/test/test_typeobject.py
+++ b/pypy/objspace/std/test/test_typeobject.py
@@ -1034,6 +1034,85 @@
         A.__dict__['x'] = 5
         assert A.x == 5
 
+    def test_metaclass_calc(self):
+        """
+        # issue1294232: correct metaclass calculation
+        new_calls = []  # to check the order of __new__ calls
+        class AMeta(type):
+            @staticmethod
+            def __new__(mcls, name, bases, ns):
+                new_calls.append('AMeta')
+                return super().__new__(mcls, name, bases, ns)
+            @classmethod
+            def __prepare__(mcls, name, bases):
+                return {}
+
+        class BMeta(AMeta):
+            @staticmethod
+            def __new__(mcls, name, bases, ns):
+                new_calls.append('BMeta')
+                return super().__new__(mcls, name, bases, ns)
+            @classmethod
+            def __prepare__(mcls, name, bases):
+                ns = super().__prepare__(name, bases)
+                ns['BMeta_was_here'] = True
+                return ns
+
+        class A(metaclass=AMeta):
+            pass
+        assert ['AMeta'] == new_calls
+        new_calls[:] = []
+
+        class B(metaclass=BMeta):
+            pass
+        # BMeta.__new__ calls AMeta.__new__ with super:
+        assert ['BMeta', 'AMeta'] == new_calls
+        new_calls[:] = []
+
+        class C(A, B):
+            pass
+        # The most derived metaclass is BMeta:
+        assert ['BMeta', 'AMeta'] == new_calls
+        new_calls[:] = []
+        # BMeta.__prepare__ should've been called:
+        assert 'BMeta_was_here' in C.__dict__
+
+        # The order of the bases shouldn't matter:
+        class C2(B, A):
+            pass
+        assert ['BMeta', 'AMeta'] == new_calls
+        new_calls[:] = []
+        assert 'BMeta_was_here' in C2.__dict__
+
+        # Check correct metaclass calculation when a metaclass is declared:
+        class D(C, metaclass=type):
+            pass
+        assert ['BMeta', 'AMeta'] == new_calls
+        new_calls[:] = []
+        assert 'BMeta_was_here' in D.__dict__
+
+        class E(C, metaclass=AMeta):
+            pass
+        assert ['BMeta', 'AMeta'] == new_calls
+        new_calls[:] = []
+        assert 'BMeta_was_here' in E.__dict__
+
+        # Special case: the given metaclass isn't a class,
+        # so there is no metaclass calculation.
+        marker = object()
+        def func(*args, **kwargs):
+            return marker
+        class X(metaclass=func):
+            pass
+        class Y(object, metaclass=func):
+            pass
+        class Z(D, metaclass=func):
+            pass
+        assert marker is X
+        assert marker is Y
+        assert marker is Z
+        """
+
 
 class AppTestWithMethodCacheCounter:
     spaceconfig = {"objspace.std.withmethodcachecounter": True}
diff --git a/pypy/objspace/std/typetype.py b/pypy/objspace/std/typetype.py
--- a/pypy/objspace/std/typetype.py
+++ b/pypy/objspace/std/typetype.py
@@ -31,20 +31,7 @@
 
     bases_w = space.fixedview(w_bases)
 
-    w_winner = w_typetype
-    for base in bases_w:
-        w_typ = space.type(base)
-        if space.is_true(space.issubtype(w_winner, w_typ)):
-            continue
-        if space.is_true(space.issubtype(w_typ, w_winner)):
-            w_winner = w_typ
-            continue
-        raise OperationError(space.w_TypeError,
-                             space.wrap("metaclass conflict: "
-                                        "the metaclass of a derived class "
-                                        "must be a (non-strict) subclass "
-                                        "of the metaclasses of all its bases"))
-
+    w_winner = _calculate_metaclass(space, w_typetype, bases_w)
     if not space.is_w(w_winner, w_typetype):
         newfunc = space.getattr(w_winner, space.wrap('__new__'))
         if not space.is_w(newfunc, space.getattr(space.w_type, space.wrap('__new__'))):
@@ -64,6 +51,23 @@
     w_type.ready()
     return w_type
 
+def _calculate_metaclass(space, w_metaclass, bases_w):
+    """Determine the most derived metatype"""
+    w_winner = w_metaclass
+    for base in bases_w:
+        w_typ = space.type(base)
+        if space.is_true(space.issubtype(w_winner, w_typ)):
+            continue
+        if space.is_true(space.issubtype(w_typ, w_winner)):
+            w_winner = w_typ
+            continue
+        raise OperationError(space.w_TypeError,
+                             space.wrap("metaclass conflict: "
+                                        "the metaclass of a derived class "
+                                        "must be a (non-strict) subclass "
+                                        "of the metaclasses of all its bases"))
+    return w_winner
+
 def _precheck_for_new(space, w_type):
     from pypy.objspace.std.typeobject import W_TypeObject
     if not isinstance(w_type, W_TypeObject):


More information about the pypy-commit mailing list