diff -u -r -d cpython.8568762381b4/Include/object.h cpython/Include/object.h --- cpython.8568762381b4/Include/object.h 2011-04-21 23:42:32.000000000 +0200 +++ cpython/Include/object.h 2011-04-21 23:42:32.000000000 +0200 @@ -449,6 +449,7 @@ #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, char *, PyObject **); +PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject *); #endif PyAPI_FUNC(unsigned int) PyType_ClearCache(void); PyAPI_FUNC(void) PyType_Modified(PyTypeObject *); diff -u -r -d cpython.8568762381b4/Lib/test/test_descr.py cpython/Lib/test/test_descr.py --- cpython.8568762381b4/Lib/test/test_descr.py 2011-04-21 23:42:32.000000000 +0200 +++ cpython/Lib/test/test_descr.py 2011-04-21 23:42:32.000000000 +0200 @@ -625,6 +625,99 @@ # The most derived metaclass of D is A rather than type. class D(B, C): pass + self.assertIs(A, type(D)) + + # 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 + self.assertEqual(['AMeta'], new_calls) + new_calls.clear() + + class B(metaclass=BMeta): + pass + # BMeta.__new__ calls AMeta.__new__ with super: + self.assertEqual(['BMeta', 'AMeta'], new_calls) + new_calls.clear() + + class C(A, B): + pass + # The most derived metaclass is BMeta: + self.assertEqual(['BMeta', 'AMeta'], new_calls) + new_calls.clear() + # BMeta.__prepare__ should've been called: + self.assertIn('BMeta_was_here', C.__dict__) + + # The order of the bases shouldn't matter: + class C2(B, A): + pass + self.assertEqual(['BMeta', 'AMeta'], new_calls) + new_calls.clear() + self.assertIn('BMeta_was_here', C2.__dict__) + + # Check correct metaclass calculation when a metaclass is declared: + class D(C, metaclass=type): + pass + self.assertEqual(['BMeta', 'AMeta'], new_calls) + new_calls.clear() + self.assertIn('BMeta_was_here', D.__dict__) + + class E(C, metaclass=AMeta): + pass + self.assertEqual(['BMeta', 'AMeta'], new_calls) + new_calls.clear() + self.assertIn('BMeta_was_here', 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 + self.assertIs(marker, X) + self.assertIs(marker, Y) + self.assertIs(marker, Z) + + # Special case: the given metaclass is + # a class, but not a descendant of type, + # so there is no metaclass calculation. + class NotMeta: + def __new__(*args, **kwargs): + return marker + class X(metaclass=NotMeta): + pass + class Y(object, metaclass=NotMeta): + pass + class Z(D, metaclass=NotMeta): + pass + self.assertIs(marker, X) + self.assertIs(marker, Y) + self.assertIs(marker, Z) def test_module_subclasses(self): # Testing Python subclass of module... diff -u -r -d cpython.8568762381b4/Objects/typeobject.c cpython/Objects/typeobject.c --- cpython.8568762381b4/Objects/typeobject.c 2011-04-21 23:42:32.000000000 +0200 +++ cpython/Objects/typeobject.c 2011-04-21 23:42:32.000000000 +0200 @@ -1910,6 +1910,42 @@ return type->tp_flags; } +/* Determine the most derived metatype. */ +PyTypeObject * +_PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases) +{ + Py_ssize_t i, nbases; + PyTypeObject *winner; + PyObject *tmp; + PyTypeObject *tmptype; + + /* Determine the proper metatype to deal with this, + and check for metatype conflicts while we're at it. + Note that if some other metatype wins to contract, + it's possible that its instances are not types. */ + + nbases = PyTuple_GET_SIZE(bases); + winner = metatype; + for (i = 0; i < nbases; i++) { + tmp = PyTuple_GET_ITEM(bases, i); + tmptype = Py_TYPE(tmp); + if (PyType_IsSubtype(winner, tmptype)) + continue; + if (PyType_IsSubtype(tmptype, winner)) { + winner = tmptype; + continue; + } + /* else: */ + PyErr_SetString(PyExc_TypeError, + "metaclass conflict: " + "the metaclass of a derived class " + "must be a (non-strict) subclass " + "of the metaclasses of all its bases"); + return NULL; + } + return winner; +} + static PyObject * type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { @@ -1953,28 +1989,12 @@ &PyDict_Type, &dict)) return NULL; - /* Determine the proper metatype to deal with this, - and check for metatype conflicts while we're at it. - Note that if some other metatype wins to contract, - it's possible that its instances are not types. */ - nbases = PyTuple_GET_SIZE(bases); - winner = metatype; - for (i = 0; i < nbases; i++) { - tmp = PyTuple_GET_ITEM(bases, i); - tmptype = Py_TYPE(tmp); - if (PyType_IsSubtype(winner, tmptype)) - continue; - if (PyType_IsSubtype(tmptype, winner)) { - winner = tmptype; - continue; - } - PyErr_SetString(PyExc_TypeError, - "metaclass conflict: " - "the metaclass of a derived class " - "must be a (non-strict) subclass " - "of the metaclasses of all its bases"); + /* Determine the proper metatype to deal with this: */ + winner = _PyType_CalculateMetaclass(metatype, bases); + if (winner == NULL) { return NULL; } + if (winner != metatype) { if (winner->tp_new != type_new) /* Pass it to the winner */ return winner->tp_new(winner, args, kwds); @@ -1982,6 +2002,7 @@ } /* Adjust for empty tuple bases */ + nbases = PyTuple_GET_SIZE(bases); if (nbases == 0) { bases = PyTuple_Pack(1, &PyBaseObject_Type); if (bases == NULL) diff -u -r -d cpython.8568762381b4/Python/bltinmodule.c cpython/Python/bltinmodule.c --- cpython.8568762381b4/Python/bltinmodule.c 2011-04-21 23:42:32.000000000 +0200 +++ cpython/Python/bltinmodule.c 2011-04-21 23:42:32.000000000 +0200 @@ -35,9 +35,10 @@ static PyObject * builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds) { - PyObject *func, *name, *bases, *mkw, *meta, *prep, *ns, *cell; + PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *cell; PyObject *cls = NULL; Py_ssize_t nargs; + int ismclass; assert(args != NULL); if (!PyTuple_Check(args)) { @@ -81,17 +82,37 @@ Py_DECREF(bases); return NULL; } + /* metaclass is explicitly given, check if it's + indeed a class, and indeed a descendant of type: */ + ismclass = (PyType_Check(meta) && + PyType_IsSubtype((PyTypeObject *)meta, &PyType_Type)); } } if (meta == NULL) { - if (PyTuple_GET_SIZE(bases) == 0) - meta = (PyObject *) (&PyType_Type); - else { - PyObject *base0 = PyTuple_GET_ITEM(bases, 0); - meta = (PyObject *) (base0->ob_type); - } + meta = (PyObject *) (&PyType_Type); Py_INCREF(meta); + ismclass = 1; /* meta is really a metaclass */ } + if (ismclass) { + /* meta is really a metaclass, so check for a more + derived metaclass, or possible metaclass conflicts: */ + winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta, + bases); + if (winner == NULL) { + Py_DECREF(meta); + Py_XDECREF(mkw); + Py_DECREF(bases); + return NULL; + } + if (winner != meta) { + Py_DECREF(meta); + meta = winner; + Py_INCREF(meta); + } + } + /* else: meta is not a class, or not a descendant + of type, so we cannot do the metaclass calculation, + so we will use the explicitly given object as it is */ prep = PyObject_GetAttrString(meta, "__prepare__"); if (prep == NULL) { if (PyErr_ExceptionMatches(PyExc_AttributeError)) {