[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