mirror of https://github.com/python/cpython
Issue 1294232: Fix errors in metaclass calculation affecting some cases of metaclass inheritance. Patch by Daniel Urban.
This commit is contained in:
parent
711f87ca7d
commit
de31b191e5
|
@ -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 *);
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Reference in New Issue