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
|
#ifndef Py_LIMITED_API
|
||||||
PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
|
PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
|
||||||
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, char *, PyObject **);
|
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, char *, PyObject **);
|
||||||
|
PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject *);
|
||||||
#endif
|
#endif
|
||||||
PyAPI_FUNC(unsigned int) PyType_ClearCache(void);
|
PyAPI_FUNC(unsigned int) PyType_ClearCache(void);
|
||||||
PyAPI_FUNC(void) PyType_Modified(PyTypeObject *);
|
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.
|
# The most derived metaclass of D is A rather than type.
|
||||||
class D(B, C):
|
class D(B, C):
|
||||||
pass
|
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):
|
def test_module_subclasses(self):
|
||||||
# Testing Python subclass of module...
|
# Testing Python subclass of module...
|
||||||
|
|
|
@ -10,6 +10,10 @@ What's New in Python 3.2.3?
|
||||||
Core and Builtins
|
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
|
- Issue #12604: VTRACE macro expanded to no-op in _sre.c to avoid compiler
|
||||||
warnings. Patch by Josh Triplett and Petri Lehtinen.
|
warnings. Patch by Josh Triplett and Petri Lehtinen.
|
||||||
|
|
||||||
|
|
|
@ -1912,6 +1912,42 @@ PyType_GetFlags(PyTypeObject *type)
|
||||||
return type->tp_flags;
|
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 *
|
static PyObject *
|
||||||
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
@ -1955,28 +1991,12 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
&PyDict_Type, &dict))
|
&PyDict_Type, &dict))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Determine the proper metatype to deal with this,
|
/* Determine the proper metatype to deal with this: */
|
||||||
and check for metatype conflicts while we're at it.
|
winner = _PyType_CalculateMetaclass(metatype, bases);
|
||||||
Note that if some other metatype wins to contract,
|
if (winner == NULL) {
|
||||||
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");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (winner != metatype) {
|
if (winner != metatype) {
|
||||||
if (winner->tp_new != type_new) /* Pass it to the winner */
|
if (winner->tp_new != type_new) /* Pass it to the winner */
|
||||||
return winner->tp_new(winner, args, kwds);
|
return winner->tp_new(winner, args, kwds);
|
||||||
|
@ -1984,6 +2004,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adjust for empty tuple bases */
|
/* Adjust for empty tuple bases */
|
||||||
|
nbases = PyTuple_GET_SIZE(bases);
|
||||||
if (nbases == 0) {
|
if (nbases == 0) {
|
||||||
bases = PyTuple_Pack(1, &PyBaseObject_Type);
|
bases = PyTuple_Pack(1, &PyBaseObject_Type);
|
||||||
if (bases == NULL)
|
if (bases == NULL)
|
||||||
|
|
|
@ -35,9 +35,10 @@ int Py_HasFileSystemDefaultEncoding = 1;
|
||||||
static PyObject *
|
static PyObject *
|
||||||
builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
|
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;
|
PyObject *cls = NULL;
|
||||||
Py_ssize_t nargs, nbases;
|
Py_ssize_t nargs, nbases;
|
||||||
|
int isclass;
|
||||||
|
|
||||||
assert(args != NULL);
|
assert(args != NULL);
|
||||||
if (!PyTuple_Check(args)) {
|
if (!PyTuple_Check(args)) {
|
||||||
|
@ -82,17 +83,42 @@ builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
Py_DECREF(bases);
|
Py_DECREF(bases);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
/* metaclass is explicitly given, check if it's indeed a class */
|
||||||
|
isclass = PyType_Check(meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (meta == NULL) {
|
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);
|
meta = (PyObject *) (&PyType_Type);
|
||||||
|
}
|
||||||
|
/* else get the type of the first base */
|
||||||
else {
|
else {
|
||||||
PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
|
PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
|
||||||
meta = (PyObject *) (base0->ob_type);
|
meta = (PyObject *) (base0->ob_type);
|
||||||
}
|
}
|
||||||
Py_INCREF(meta);
|
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__");
|
prep = PyObject_GetAttrString(meta, "__prepare__");
|
||||||
if (prep == NULL) {
|
if (prep == NULL) {
|
||||||
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
||||||
|
|
Loading…
Reference in New Issue