summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/object.h1
-rw-r--r--Lib/test/test_descr.py168
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/typeobject.c61
-rw-r--r--Python/bltinmodule.c30
5 files changed, 242 insertions, 22 deletions
diff --git a/Include/object.h b/Include/object.h
index 3753785..2528841 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -449,6 +449,7 @@ PyAPI_FUNC(PyObject *) PyType_GenericNew(PyTypeObject *,
#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 --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 587d792..b214996 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -625,6 +625,174 @@ class ClassPropertiesAndMethods(unittest.TestCase):
# 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[:] = []
+
+ class B(metaclass=BMeta):
+ pass
+ # BMeta.__new__ calls AMeta.__new__ with super:
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
+ new_calls[:] = []
+
+ class C(A, B):
+ pass
+ # The most derived metaclass is BMeta:
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
+ new_calls[:] = []
+ # 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[:] = []
+ 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[:] = []
+ self.assertIn('BMeta_was_here', D.__dict__)
+
+ class E(C, metaclass=AMeta):
+ pass
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
+ new_calls[:] = []
+ 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)
+
+ # The given metaclass is a class,
+ # but not a descendant of type.
+ prepare_calls = [] # to track __prepare__ calls
+ class ANotMeta:
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('ANotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('ANotMeta')
+ return {}
+ class BNotMeta(ANotMeta):
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('BNotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('BNotMeta')
+ return super().__prepare__(name, bases)
+
+ class A(metaclass=ANotMeta):
+ pass
+ self.assertIs(ANotMeta, type(A))
+ self.assertEqual(['ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+ self.assertEqual(['ANotMeta'], new_calls)
+ new_calls[:] = []
+
+ class B(metaclass=BNotMeta):
+ pass
+ self.assertIs(BNotMeta, type(B))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+
+ class C(A, B):
+ pass
+ self.assertIs(BNotMeta, type(C))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+
+ class C2(B, A):
+ pass
+ self.assertIs(BNotMeta, type(C2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+
+ # This is a TypeError, because of a metaclass conflict:
+ # BNotMeta is neither a subclass, nor a superclass of type
+ with self.assertRaises(TypeError):
+ class D(C, metaclass=type):
+ pass
+
+ class E(C, metaclass=ANotMeta):
+ pass
+ self.assertIs(BNotMeta, type(E))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+
+ class F(object(), C):
+ pass
+ self.assertIs(BNotMeta, type(F))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+
+ class F2(C, object()):
+ pass
+ self.assertIs(BNotMeta, type(F2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls[:] = []
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls[:] = []
+
+ # TypeError: BNotMeta is neither a
+ # subclass, nor a superclass of int
+ with self.assertRaises(TypeError):
+ class X(C, int()):
+ pass
+ with self.assertRaises(TypeError):
+ class X(int(), C):
+ pass
def test_module_subclasses(self):
# Testing Python subclass of module...
diff --git a/Misc/NEWS b/Misc/NEWS
index fbf3b8d..c078623 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,10 @@ What's New in Python 3.2.3?
Core and Builtins
-----------------
+- Issue #1294232: In a few cases involving metaclass inheritance, the
+ interpreter would sometimes invoke the wrong metaclass when building a new
+ class object. These cases now behave correctly. Patch by Daniel Urban.
+
- Issue #12604: VTRACE macro expanded to no-op in _sre.c to avoid compiler
warnings. Patch by Josh Triplett and Petri Lehtinen.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 856a4a5..9f62401 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -1912,6 +1912,42 @@ PyType_GetFlags(PyTypeObject *type)
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)
{
@@ -1955,28 +1991,12 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
&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);
@@ -1984,6 +2004,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
}
/* Adjust for empty tuple bases */
+ nbases = PyTuple_GET_SIZE(bases);
if (nbases == 0) {
bases = PyTuple_Pack(1, &PyBaseObject_Type);
if (bases == NULL)
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 6258167..9c50b88 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -35,9 +35,10 @@ int Py_HasFileSystemDefaultEncoding = 1;
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, nbases;
+ int isclass;
assert(args != NULL);
if (!PyTuple_Check(args)) {
@@ -82,17 +83,42 @@ builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
Py_DECREF(bases);
return NULL;
}
+ /* metaclass is explicitly given, check if it's indeed a class */
+ isclass = PyType_Check(meta);
}
}
if (meta == NULL) {
- if (PyTuple_GET_SIZE(bases) == 0)
+ /* if there are no bases, use type: */
+ if (PyTuple_GET_SIZE(bases) == 0) {
meta = (PyObject *) (&PyType_Type);
+ }
+ /* else get the type of the first base */
else {
PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
meta = (PyObject *) (base0->ob_type);
}
Py_INCREF(meta);
+ isclass = 1; /* meta is really a class */
+ }
+ if (isclass) {
+ /* meta is really a class, 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, 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)) {