diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 8d36744f273..013574e46a3 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -171,8 +171,13 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P // Ensure that the base __init__ function(s) were called for (const auto &vh : values_and_holders(instance)) { if (!vh.holder_constructed()) { - PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__", - vh.type->type->tp_name); + auto message = "%.200s.__init__() must be called when overriding __init__"; +#if !defined(PYPY_VERSION) + if (vh.type->type->tp_init == get_internals().default_object_init()) { + message = "%.200s has no __init__ and cannot be used as a base class from Python"; + } +#endif + PyErr_Format(PyExc_TypeError, message, vh.type->type->tp_name); Py_DECREF(self); return nullptr; } @@ -302,6 +307,8 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, /// An `__init__` function constructs the C++ object. Users should provide at least one /// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the /// following default function will be used which simply throws an exception. +/// +/// Use `get_internals().default_object_init()` instead of using this function directly extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { PyTypeObject *type = Py_TYPE(self); std::string msg; @@ -620,7 +627,7 @@ inline PyObject* make_new_python_type(const type_record &rec) { type->tp_bases = bases.release().ptr(); /* Don't inherit base __init__ */ - type->tp_init = pybind11_object_init; + type->tp_init = internals.default_object_init(); /* Supported protocols */ type->tp_as_number = &heap_type->as_number; diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index cf40e9fe995..ee02bb7e3fa 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -121,6 +121,11 @@ struct internals { PYBIND11_TLS_FREE(tstate); } #endif + + inline initproc default_object_init() { + auto heap_type = (PyHeapTypeObject*)instance_base; + return heap_type->ht_type.tp_init; + } }; /// Additional type information which does not fit into the PyTypeObject. diff --git a/tests/test_class.py b/tests/test_class.py index 200deee2661..8911cdf2182 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -132,6 +132,21 @@ def __init__(self): expected = "m.class_.Hamster.__init__() must be called when overriding __init__" assert msg(exc_info.value) == expected + # Base doesn't have __init__ + class ChChimera(m.Chimera): + def __init__(self): + pass + + with pytest.raises(TypeError) as exc_info: + ChChimera() + if env.PYPY: + # can't detect no __init__ in PyPy + expected = "Chimera.__init__() must be called when overriding __init__" + else: + expected = \ + "m.class_.Chimera has no __init__ and cannot be used as a base class from Python" + assert msg(exc_info.value) == expected + def test_automatic_upcasting(): assert type(m.return_class_1()).__name__ == "DerivedClass1"