diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 50225623fe52db..c44414f3ccb1a6 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -878,7 +878,7 @@ static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) { extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems); PyAPI_FUNC(PyObject *) _PyType_NewManagedObject(PyTypeObject *type); -extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *); +extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *, PyObject *); extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *); extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int); extern int _PyObject_SetAttributeErrorContext(PyObject *v, PyObject* name); diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 9011e0e1962820..042a37519b2a5d 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2562,5 +2562,19 @@ def collate_results(raw): self.assertEqual(interp_results, {}) +class TestMetaclassConflict(unittest.TestCase): + def test_metaclass_conflict_message(self): + class MetaFoo(type): pass + class MetaBar(type): pass + class Bar(metaclass=MetaBar): pass + with self.assertRaises(TypeError) as cm: + class Foo(Bar, metaclass=MetaFoo): pass + msg = str(cm.exception) + self.assertIn("Metaclass conflict", msg) + self.assertIn("MetaFoo", msg) + self.assertIn("MetaBar", msg) + self.assertIn("Bar", msg) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-20-47-06.gh-issue-skip.CO_d25.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-20-47-06.gh-issue-skip.CO_d25.rst new file mode 100644 index 00000000000000..b4db42fc84c534 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-20-47-06.gh-issue-skip.CO_d25.rst @@ -0,0 +1 @@ +Metaclass incompatibility error message now more clearly explains the issue including the name of the offending baseclass and the conflicting metaclasses. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-metaclass-error.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-metaclass-error.rst new file mode 100644 index 00000000000000..9665446606e16c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-metaclass-error.rst @@ -0,0 +1,2 @@ +Metaclass incompatibility error message now more clearly explains the issue +including the name of the offending baseclass and the conflicting metaclasses. \ No newline at end of file diff --git a/Objects/typeobject.c b/Objects/typeobject.c index db923c164774b7..441b6cedb1c7b4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4105,13 +4105,14 @@ PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) /* Determine the most derived metatype. */ PyTypeObject * -_PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases) +_PyType_CalculateMetaclass(PyTypeObject *metatype, + PyObject *bases, + PyObject *name) { 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, @@ -4129,11 +4130,43 @@ _PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases) 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"); + PyObject *declared_meta_name = NULL; + PyObject *base_class_name = NULL; + PyObject *base_meta_name = NULL; + PyObject *format_result = NULL; + + declared_meta_name = PyObject_GetAttrString((PyObject *)winner, "__name__"); + base_class_name = PyObject_GetAttrString(tmp, "__name__"); + base_meta_name = PyObject_GetAttrString((PyObject *)tmptype, "__name__"); + + if (declared_meta_name && base_class_name && base_meta_name) { + format_result = PyUnicode_FromFormat( + "Metaclass conflict while defining class: '%U'\n" + "- Declared metaclass: '%U'\n" + "- Incompatible base class: '%U'\n" + "- That base is derived from metaclass: '%U'\n" + "All base classes must be based on classes derived from the same " + "metaclass or a subclass thereof.", + name, + declared_meta_name, + base_class_name, + base_meta_name + ); + if (format_result) { + PyErr_SetObject(PyExc_TypeError, format_result); + Py_DECREF(format_result); + } + } else { + /* fallback */ + 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"); + } + + Py_XDECREF(declared_meta_name); + Py_XDECREF(base_class_name); + Py_XDECREF(base_meta_name); return NULL; } return winner; @@ -4917,7 +4950,7 @@ type_new_get_bases(type_new_ctx *ctx, PyObject **type) // Search the bases for the proper metatype to deal with this PyTypeObject *winner; - winner = _PyType_CalculateMetaclass(ctx->metatype, ctx->bases); + winner = _PyType_CalculateMetaclass(ctx->metatype, ctx->bases, ctx->name); if (winner == NULL) { return -1; } @@ -5333,10 +5366,19 @@ PyType_FromMetaclass( if (!metaclass) { metaclass = &PyType_Type; } - metaclass = _PyType_CalculateMetaclass(metaclass, bases); + /* Get resolve the name from 'spec' */ + PyObject *name = NULL; + if (spec && spec->name) { + name = PyUnicode_FromString(spec->name); + } + + metaclass = _PyType_CalculateMetaclass(metaclass, bases, name); if (metaclass == NULL) { goto finally; } + /* Remove name again */ + Py_XDECREF(name); + if (!PyType_Check(metaclass)) { PyErr_Format(PyExc_TypeError, "Metaclass '%R' is not a subclass of 'type'.", diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index e08c63924ca16d..94ccfbc08ae784 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -169,7 +169,7 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs, /* meta is really a class, so check for a more derived metaclass, or possible metaclass conflicts: */ winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta, - bases); + bases,name); if (winner == NULL) { goto error; }