summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2011-10-23 12:36:42 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2011-10-23 12:36:42 (GMT)
commit9715d26305f7cf6c4db91f3670f1b7e99f04dd2d (patch)
tree0bd6a35f21a42e42ac2bdeb09b0c5a3b9eb3188a
parenta0e0e232993b87fe446f95d6583d947ae01bf517 (diff)
parentde31b191e570d00ed7917c7f9ea28af3e770c16d (diff)
downloadcpython-9715d26305f7cf6c4db91f3670f1b7e99f04dd2d.zip
cpython-9715d26305f7cf6c4db91f3670f1b7e99f04dd2d.tar.gz
cpython-9715d26305f7cf6c4db91f3670f1b7e99f04dd2d.tar.bz2
Merge issue 1294232 patch from 3.2
-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.c33
5 files changed, 244 insertions, 23 deletions
diff --git a/Include/object.h b/Include/object.h
index cf231af..648c9cb 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 2a9f880..15219db 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.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)
+
+ # 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.clear()
+ self.assertEqual(['ANotMeta'], new_calls)
+ new_calls.clear()
+
+ class B(metaclass=BNotMeta):
+ pass
+ self.assertIs(BNotMeta, type(B))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls.clear()
+
+ class C(A, B):
+ pass
+ self.assertIs(BNotMeta, type(C))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+
+ class C2(B, A):
+ pass
+ self.assertIs(BNotMeta, type(C2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+
+ # 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.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+
+ class F(object(), C):
+ pass
+ self.assertIs(BNotMeta, type(F))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+
+ class F2(C, object()):
+ pass
+ self.assertIs(BNotMeta, type(F2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
+ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
+ prepare_calls.clear()
+
+ # 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 b9ff2fa..8e6eff7 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
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 #12753: Add support for Unicode name aliases and named sequences.
Both :func:`unicodedata.lookup()` and '\N{...}' now resolve aliases,
and :func:`unicodedata.lookup()` resolves named sequences too.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 130e8fe..6b60c8f 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -1915,6 +1915,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)
{
@@ -1958,28 +1994,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);
@@ -1987,6 +2007,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 e68f025..fd242b7 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -38,9 +38,10 @@ _Py_IDENTIFIER(flush);
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;
+ Py_ssize_t nargs, nbases;
+ int isclass;
_Py_IDENTIFIER(__prepare__);
assert(args != NULL);
@@ -85,17 +86,43 @@ 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_GetAttrId(meta, &PyId___prepare__);
if (prep == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {