Commit 08cbe8df by Dean Moldovan Committed by Wenzel Jakob

Make all classes with the same instance size derive from a common base

In order to fully satisfy Python's inheritance type layout requirements,
all types should have a common 'solid' base. A solid base is one which
has the same instance size as the derived type (not counting the space
required for the optional `dict_ptr` and `weakrefs_ptr`). Thus, `object`
does not qualify as a solid base for pybind11 types and this can lead to
issues with multiple inheritance.

To get around this, new base types are created: one per unique instance
size. There is going to be very few of these bases. They ensure Python's
MRO checks will pass when multiple bases are involved.
parent c91f8bd6
......@@ -26,6 +26,7 @@ struct type_info {
PyTypeObject *type;
size_t type_size;
void (*init_holder)(PyObject *, const void *);
void (*dealloc)(PyObject *);
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions;
std::vector<std::pair<const std::type_info *, void *(*)(void *)>> implicit_casts;
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
......
......@@ -85,6 +85,34 @@ inline PyTypeObject *make_static_property_type() {
#endif // PYPY
/** Inheriting from multiple C++ types in Python is not supported -- set an error instead.
A Python definition (`class C(A, B): pass`) will call `tp_new` so we check for multiple
C++ bases here. On the other hand, C++ type definitions (`py::class_<C, A, B>(m, "C")`)
don't not use `tp_new` and will not trigger this error. */
extern "C" inline PyObject *pybind11_meta_new(PyTypeObject *metaclass, PyObject *args,
PyObject *kwargs) {
PyObject *bases = PyTuple_GetItem(args, 1); // arguments: (name, bases, dict)
if (!bases)
return nullptr;
auto &internals = get_internals();
auto num_cpp_bases = 0;
for (auto base : reinterpret_borrow<tuple>(bases)) {
auto base_type = (PyTypeObject *) base.ptr();
auto instance_size = static_cast<size_t>(base_type->tp_basicsize);
if (PyObject_IsSubclass(base.ptr(), internals.get_base(instance_size)))
++num_cpp_bases;
}
if (num_cpp_bases > 1) {
PyErr_SetString(PyExc_TypeError, "Can't inherit from multiple C++ classes in Python."
"Use py::class_ to define the class in C++ instead.");
return nullptr;
} else {
return PyType_Type.tp_new(metaclass, args, kwargs);
}
}
/** Types with static properties need to handle `Type.static_prop = x` in a specific way.
By default, Python replaces the `static_property` itself, but for wrapped C++ types
we need to call `static_property.__set__()` in order to propagate the new value to
......@@ -135,6 +163,8 @@ inline PyTypeObject* make_default_metaclass() {
type->tp_name = name;
type->tp_base = &PyType_Type;
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_new = pybind11_meta_new;
type->tp_setattro = pybind11_meta_setattro;
if (PyType_Ready(type) < 0)
......@@ -143,5 +173,328 @@ inline PyTypeObject* make_default_metaclass() {
return type;
}
/// Instance creation function for all pybind11 types. It only allocates space for the
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
PyObject *self = type->tp_alloc(type, 0);
auto instance = (instance_essentials<void> *) self;
auto tinfo = get_type_info(type);
instance->value = ::operator new(tinfo->type_size);
instance->owned = true;
instance->holder_constructed = false;
get_internals().registered_instances.emplace(instance->value, self);
return self;
}
/// 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.
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
PyTypeObject *type = Py_TYPE(self);
std::string msg;
#if defined(PYPY_VERSION)
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
#endif
msg += type->tp_name;
msg += ": No constructor defined!";
PyErr_SetString(PyExc_TypeError, msg.c_str());
return -1;
}
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
auto instance = (instance_essentials<void> *) self;
if (instance->value) {
auto type = Py_TYPE(self);
get_type_info(type)->dealloc(self);
auto &registered_instances = get_internals().registered_instances;
auto range = registered_instances.equal_range(instance->value);
bool found = false;
for (auto it = range.first; it != range.second; ++it) {
if (type == Py_TYPE(it->second)) {
registered_instances.erase(it);
found = true;
break;
}
}
if (!found)
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
if (instance->weakrefs)
PyObject_ClearWeakRefs(self);
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
if (dict_ptr)
Py_CLEAR(*dict_ptr);
}
Py_TYPE(self)->tp_free(self);
}
/** Create a type which can be used as a common base for all classes with the same
instance size, i.e. all classes with the same `sizeof(holder_type)`. This is
needed in order to satisfy Python's requirements for multiple inheritance.
Return value: New reference. */
inline PyObject *make_object_base_type(size_t instance_size) {
auto name = "pybind11_object_" + std::to_string(instance_size);
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name.c_str()));
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type)
pybind11_fail("make_object_base_type(): error allocating type!");
heap_type->ht_name = name_obj.inc_ref().ptr();
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif
auto type = &heap_type->ht_type;
type->tp_name = strdup(name.c_str());
type->tp_base = &PyBaseObject_Type;
type->tp_basicsize = static_cast<ssize_t>(instance_size);
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_new = pybind11_object_new;
type->tp_init = pybind11_object_init;
type->tp_dealloc = pybind11_object_dealloc;
/* Support weak references (needed for the keep_alive feature) */
type->tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
if (PyType_Ready(type) < 0)
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string());
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
return (PyObject *) heap_type;
}
/** Return the appropriate base type for the given instance size. The results are cached
in `internals.bases` so that only a single base is ever created for any size value.
Return value: Borrowed reference. */
inline PyObject *internals::get_base(size_t instance_size) {
auto it = bases.find(instance_size);
if (it != bases.end()) {
return it->second;
} else {
auto base = make_object_base_type(instance_size);
bases[instance_size] = base;
return base;
}
}
/// dynamic_attr: Support for `d = instance.__dict__`.
extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
PyObject *&dict = *_PyObject_GetDictPtr(self);
if (!dict)
dict = PyDict_New();
Py_XINCREF(dict);
return dict;
}
/// dynamic_attr: Support for `instance.__dict__ = dict()`.
extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
if (!PyDict_Check(new_dict)) {
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
Py_TYPE(new_dict)->tp_name);
return -1;
}
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_INCREF(new_dict);
Py_CLEAR(dict);
dict = new_dict;
return 0;
}
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_VISIT(dict);
return 0;
}
/// dynamic_attr: Allow the GC to clear the dictionary.
extern "C" inline int pybind11_clear(PyObject *self) {
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_CLEAR(dict);
return 0;
}
/// Give instances of this type a `__dict__` and opt into garbage collection.
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
auto type = &heap_type->ht_type;
#if defined(PYPY_VERSION)
pybind11_fail(std::string(type->tp_name) + ": dynamic attributes are "
"currently not supported in "
"conjunction with PyPy!");
#endif
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
type->tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
type->tp_traverse = pybind11_traverse;
type->tp_clear = pybind11_clear;
static PyGetSetDef getset[] = {
{const_cast<char*>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr}
};
type->tp_getset = getset;
}
/// buffer_protocol: Fill in the view as specified by flags.
extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
auto tinfo = get_type_info(Py_TYPE(obj));
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
if (view)
view->obj = nullptr;
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
return -1;
}
memset(view, 0, sizeof(Py_buffer));
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
view->obj = obj;
view->ndim = 1;
view->internal = info;
view->buf = info->ptr;
view->itemsize = (ssize_t) info->itemsize;
view->len = view->itemsize;
for (auto s : info->shape)
view->len *= s;
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
view->format = const_cast<char *>(info->format.c_str());
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
view->ndim = (int) info->ndim;
view->strides = (ssize_t *) &info->strides[0];
view->shape = (ssize_t *) &info->shape[0];
}
Py_INCREF(view->obj);
return 0;
}
/// buffer_protocol: Release the resources of the buffer.
extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) {
delete (buffer_info *) view->internal;
}
/// Give this type a buffer interface.
inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer;
#if PY_MAJOR_VERSION < 3
heap_type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
#endif
heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer;
heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer;
}
/** Create a brand new Python type according to the `type_record` specification.
Return value: New reference. */
inline PyObject* make_new_python_type(const type_record &rec) {
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
auto ht_qualname = name;
if (rec.scope && hasattr(rec.scope, "__qualname__")) {
ht_qualname = reinterpret_steal<object>(
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
}
#endif
object module;
if (rec.scope) {
if (hasattr(rec.scope, "__module__"))
module = rec.scope.attr("__module__");
else if (hasattr(rec.scope, "__name__"))
module = rec.scope.attr("__name__");
}
#if !defined(PYPY_VERSION)
const auto full_name = module ? str(module).cast<std::string>() + "." + rec.name
: std::string(rec.name);
#else
const auto full_name = std::string(rec.name);
#endif
char *tp_doc = nullptr;
if (rec.doc && options::show_user_defined_docstrings()) {
/* Allocate memory for docstring (using PyObject_MALLOC, since
Python will free this later on) */
size_t size = strlen(rec.doc) + 1;
tp_doc = (char *) PyObject_MALLOC(size);
memcpy((void *) tp_doc, rec.doc, size);
}
auto &internals = get_internals();
auto bases = tuple(rec.bases);
auto base = (bases.size() == 0) ? internals.get_base(rec.instance_size)
: bases[0].ptr();
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type)
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
heap_type->ht_name = name.release().ptr();
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
heap_type->ht_qualname = ht_qualname.release().ptr();
#endif
auto type = &heap_type->ht_type;
type->tp_name = strdup(full_name.c_str());
type->tp_doc = tp_doc;
type->tp_base = (PyTypeObject *) handle(base).inc_ref().ptr();
type->tp_basicsize = static_cast<ssize_t>(rec.instance_size);
if (bases.size() > 0)
type->tp_bases = bases.release().ptr();
/* Don't inherit base __init__ */
type->tp_init = pybind11_object_init;
/* Custom metaclass if requested (used for static properties) */
if (rec.metaclass) {
Py_INCREF(internals.default_metaclass);
Py_TYPE(type) = (PyTypeObject *) internals.default_metaclass;
}
/* Supported protocols */
type->tp_as_number = &heap_type->as_number;
type->tp_as_sequence = &heap_type->as_sequence;
type->tp_as_mapping = &heap_type->as_mapping;
/* Flags */
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
#if PY_MAJOR_VERSION < 3
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
#endif
if (rec.dynamic_attr)
enable_dynamic_attributes(heap_type);
if (rec.buffer_protocol)
enable_buffer_protocol(heap_type);
if (PyType_Ready(type) < 0)
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!");
assert(rec.dynamic_attr ? PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)
: !PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
/* Register type with the parent scope */
if (rec.scope)
setattr(rec.scope, rec.name, (PyObject *) type);
if (module) // Needed by pydoc
setattr((PyObject *) type, "__module__", module);
return (PyObject *) type;
}
NAMESPACE_END(detail)
NAMESPACE_END(pybind11)
......@@ -122,7 +122,6 @@
#define PYBIND11_SLICE_OBJECT PyObject
#define PYBIND11_FROM_STRING PyUnicode_FromString
#define PYBIND11_STR_TYPE ::pybind11::str
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_base.ob_base.ob_type
#define PYBIND11_PLUGIN_IMPL(name) \
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
#else
......@@ -141,7 +140,6 @@
#define PYBIND11_SLICE_OBJECT PySliceObject
#define PYBIND11_FROM_STRING PyString_FromString
#define PYBIND11_STR_TYPE ::pybind11::bytes
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_type
#define PYBIND11_PLUGIN_IMPL(name) \
static PyObject *pybind11_init_wrapper(); \
extern "C" PYBIND11_EXPORT void init##name() { \
......@@ -363,10 +361,14 @@ struct internals {
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
PyTypeObject *static_property_type;
PyTypeObject *default_metaclass;
std::unordered_map<size_t, PyObject *> bases; // one base type per `instance_size` (very few)
#if defined(WITH_THREAD)
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
PyInterpreterState *istate = nullptr;
#endif
/// Return the appropriate base type for the given instance size
PyObject *get_base(size_t instance_size);
};
/// Return a reference to the current 'internals' information
......
......@@ -760,196 +760,39 @@ public:
};
NAMESPACE_BEGIN(detail)
extern "C" inline PyObject *get_dict(PyObject *op, void *) {
PyObject *&dict = *_PyObject_GetDictPtr(op);
if (!dict)
dict = PyDict_New();
Py_XINCREF(dict);
return dict;
}
extern "C" inline int set_dict(PyObject *op, PyObject *new_dict, void *) {
if (!PyDict_Check(new_dict)) {
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
Py_TYPE(new_dict)->tp_name);
return -1;
}
PyObject *&dict = *_PyObject_GetDictPtr(op);
Py_INCREF(new_dict);
Py_CLEAR(dict);
dict = new_dict;
return 0;
}
static PyGetSetDef generic_getset[] = {
{const_cast<char*>("__dict__"), get_dict, set_dict, nullptr, nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr}
};
/// Generic support for creating new Python heap types
class generic_type : public object {
template <typename...> friend class class_;
public:
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
protected:
void initialize(type_record *rec) {
auto &internals = get_internals();
auto tindex = std::type_index(*(rec->type));
void initialize(const type_record &rec) {
if (rec.scope && hasattr(rec.scope, rec.name))
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) +
"\": an object with that name is already defined");
if (get_type_info(*(rec->type)))
pybind11_fail("generic_type: type \"" + std::string(rec->name) +
if (get_type_info(*rec.type))
pybind11_fail("generic_type: type \"" + std::string(rec.name) +
"\" is already registered!");
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec->name));
object scope_module;
if (rec->scope) {
if (hasattr(rec->scope, rec->name))
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec->name) +
"\": an object with that name is already defined");
if (hasattr(rec->scope, "__module__")) {
scope_module = rec->scope.attr("__module__");
} else if (hasattr(rec->scope, "__name__")) {
scope_module = rec->scope.attr("__name__");
}
}
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
/* Qualified names for Python >= 3.3 */
object scope_qualname;
if (rec->scope && hasattr(rec->scope, "__qualname__"))
scope_qualname = rec->scope.attr("__qualname__");
object ht_qualname;
if (scope_qualname)
ht_qualname = reinterpret_steal<object>(PyUnicode_FromFormat(
"%U.%U", scope_qualname.ptr(), name.ptr()));
else
ht_qualname = name;
#endif
#if !defined(PYPY_VERSION)
std::string full_name = (scope_module ? ((std::string) pybind11::str(scope_module) + "." + rec->name)
: std::string(rec->name));
#else
std::string full_name = std::string(rec->name);
#endif
size_t num_bases = rec->bases.size();
auto bases = tuple(rec->bases);
char *tp_doc = nullptr;
if (rec->doc && options::show_user_defined_docstrings()) {
/* Allocate memory for docstring (using PyObject_MALLOC, since
Python will free this later on) */
size_t size = strlen(rec->doc) + 1;
tp_doc = (char *) PyObject_MALLOC(size);
memcpy((void *) tp_doc, rec->doc, size);
}
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto type_holder = reinterpret_steal<object>(PyType_Type.tp_alloc(&PyType_Type, 0));
auto type = (PyHeapTypeObject*) type_holder.ptr();
if (!type_holder || !name)
pybind11_fail(std::string(rec->name) + ": Unable to create type object!");
m_ptr = make_new_python_type(rec);
/* Register supplemental type information in C++ dict */
detail::type_info *tinfo = new detail::type_info();
tinfo->type = (PyTypeObject *) type;
tinfo->type_size = rec->type_size;
tinfo->init_holder = rec->init_holder;
auto *tinfo = new detail::type_info();
tinfo->type = (PyTypeObject *) m_ptr;
tinfo->type_size = rec.type_size;
tinfo->init_holder = rec.init_holder;
tinfo->dealloc = rec.dealloc;
auto &internals = get_internals();
auto tindex = std::type_index(*rec.type);
tinfo->direct_conversions = &internals.direct_conversions[tindex];
tinfo->default_holder = rec->default_holder;
tinfo->default_holder = rec.default_holder;
internals.registered_types_cpp[tindex] = tinfo;
internals.registered_types_py[type] = tinfo;
/* Basic type attributes */
type->ht_type.tp_name = strdup(full_name.c_str());
type->ht_type.tp_basicsize = (ssize_t) rec->instance_size;
if (num_bases > 0) {
type->ht_type.tp_base = (PyTypeObject *) ((object) bases[0]).inc_ref().ptr();
type->ht_type.tp_bases = bases.release().ptr();
rec->multiple_inheritance |= num_bases > 1;
}
type->ht_name = name.release().ptr();
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
type->ht_qualname = ht_qualname.release().ptr();
#endif
/* Custom metaclass if requested (used for static properties) */
if (rec->metaclass)
PYBIND11_OB_TYPE(type->ht_type) = internals.default_metaclass;
/* Supported protocols */
type->ht_type.tp_as_number = &type->as_number;
type->ht_type.tp_as_sequence = &type->as_sequence;
type->ht_type.tp_as_mapping = &type->as_mapping;
/* Supported elementary operations */
type->ht_type.tp_init = (initproc) init;
type->ht_type.tp_new = (newfunc) new_instance;
type->ht_type.tp_dealloc = rec->dealloc;
/* Support weak references (needed for the keep_alive feature) */
type->ht_type.tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
/* Flags */
type->ht_type.tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
#if PY_MAJOR_VERSION < 3
type->ht_type.tp_flags |= Py_TPFLAGS_CHECKTYPES;
#endif
type->ht_type.tp_flags &= ~Py_TPFLAGS_HAVE_GC;
/* Support dynamic attributes */
if (rec->dynamic_attr) {
#if defined(PYPY_VERSION)
pybind11_fail(std::string(rec->name) + ": dynamic attributes are "
"currently not supported in "
"conunction with PyPy!");
#endif
type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_GC;
type->ht_type.tp_dictoffset = type->ht_type.tp_basicsize; // place the dict at the end
type->ht_type.tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
type->ht_type.tp_getset = generic_getset;
type->ht_type.tp_traverse = traverse;
type->ht_type.tp_clear = clear;
}
if (rec->buffer_protocol) {
type->ht_type.tp_as_buffer = &type->as_buffer;
#if PY_MAJOR_VERSION < 3
type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
#endif
type->as_buffer.bf_getbuffer = getbuffer;
type->as_buffer.bf_releasebuffer = releasebuffer;
}
type->ht_type.tp_doc = tp_doc;
internals.registered_types_py[m_ptr] = tinfo;
m_ptr = type_holder.ptr();
if (PyType_Ready(&type->ht_type) < 0)
pybind11_fail(std::string(rec->name) + ": PyType_Ready failed (" +
detail::error_string() + ")!");
if (scope_module) // Needed by pydoc
attr("__module__") = scope_module;
/* Register type with the parent scope */
if (rec->scope)
rec->scope.attr(handle(type->ht_name)) = *this;
if (rec->multiple_inheritance)
mark_parents_nonsimple(&type->ht_type);
type_holder.release();
if (rec.bases.size() > 1 || rec.multiple_inheritance)
mark_parents_nonsimple(tinfo->type);
}
/// Helper function which tags all parents of a type using mult. inheritance
......@@ -963,66 +806,6 @@ protected:
}
}
static int init(void *self, PyObject *, PyObject *) {
PyTypeObject *type = Py_TYPE(self);
std::string msg;
#if defined(PYPY_VERSION)
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
#endif
msg += type->tp_name;
msg += ": No constructor defined!";
PyErr_SetString(PyExc_TypeError, msg.c_str());
return -1;
}
static PyObject *new_instance(PyTypeObject *type, PyObject *, PyObject *) {
instance<void> *self = (instance<void> *) PyType_GenericAlloc((PyTypeObject *) type, 0);
auto tinfo = detail::get_type_info(type);
self->value = ::operator new(tinfo->type_size);
self->owned = true;
self->holder_constructed = false;
detail::get_internals().registered_instances.emplace(self->value, (PyObject *) self);
return (PyObject *) self;
}
static void dealloc(instance<void> *self) {
if (self->value) {
auto instance_type = Py_TYPE(self);
auto &registered_instances = detail::get_internals().registered_instances;
auto range = registered_instances.equal_range(self->value);
bool found = false;
for (auto it = range.first; it != range.second; ++it) {
if (instance_type == Py_TYPE(it->second)) {
registered_instances.erase(it);
found = true;
break;
}
}
if (!found)
pybind11_fail("generic_type::dealloc(): Tried to deallocate unregistered instance!");
if (self->weakrefs)
PyObject_ClearWeakRefs((PyObject *) self);
PyObject **dict_ptr = _PyObject_GetDictPtr((PyObject *) self);
if (dict_ptr)
Py_CLEAR(*dict_ptr);
}
Py_TYPE(self)->tp_free((PyObject*) self);
}
static int traverse(PyObject *op, visitproc visit, void *arg) {
PyObject *&dict = *_PyObject_GetDictPtr(op);
Py_VISIT(dict);
return 0;
}
static int clear(PyObject *op) {
PyObject *&dict = *_PyObject_GetDictPtr(op);
Py_CLEAR(dict);
return 0;
}
void install_buffer_funcs(
buffer_info *(*get_buffer)(PyObject *, void *),
void *get_buffer_data) {
......@@ -1040,37 +823,6 @@ protected:
tinfo->get_buffer_data = get_buffer_data;
}
static int getbuffer(PyObject *obj, Py_buffer *view, int flags) {
auto tinfo = detail::get_type_info(Py_TYPE(obj));
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
if (view)
view->obj = nullptr;
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
return -1;
}
memset(view, 0, sizeof(Py_buffer));
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
view->obj = obj;
view->ndim = 1;
view->internal = info;
view->buf = info->ptr;
view->itemsize = (ssize_t) info->itemsize;
view->len = view->itemsize;
for (auto s : info->shape)
view->len *= s;
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
view->format = const_cast<char *>(info->format.c_str());
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
view->ndim = (int) info->ndim;
view->strides = (ssize_t *) &info->strides[0];
view->shape = (ssize_t *) &info->shape[0];
}
Py_INCREF(view->obj);
return 0;
}
static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; }
void def_property_static_impl(const char *name,
handle fget, handle fset,
detail::function_record *rec_fget) {
......@@ -1078,7 +830,7 @@ protected:
const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
if (is_static) {
auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr)));
auto mclass = handle((PyObject *) Py_TYPE(m_ptr));
if ((PyTypeObject *) mclass.ptr() == &PyType_Type)
pybind11_fail(
......@@ -1140,7 +892,7 @@ public:
/* Process optional arguments, if any */
detail::process_attributes<Extra...>::init(extra..., &record);
detail::generic_type::initialize(&record);
detail::generic_type::initialize(record);
if (has_alias) {
auto &instances = pybind11::detail::get_internals().registered_types_cpp;
......@@ -1352,8 +1104,6 @@ private:
inst->holder.~holder_type();
else if (inst->owned)
::operator delete(inst->value);
generic_type::dealloc((detail::instance<void> *) inst);
}
static detail::function_record *get_function_record(handle h) {
......
......@@ -36,6 +36,10 @@ public:
Hamster(const std::string &name) : Pet(name, "rodent") {}
};
class Chimera : public Pet {
Chimera() : Pet("Kimmy", "chimera") {}
};
std::string pet_name_species(const Pet &pet) {
return pet.name() + " is a " + pet.species();
}
......@@ -74,6 +78,8 @@ test_initializer inheritance([](py::module &m) {
py::class_<Hamster, Pet>(m, "Hamster")
.def(py::init<std::string>());
py::class_<Chimera, Pet>(m, "Chimera");
m.def("pet_name_species", pet_name_species);
m.def("dog_bark", dog_bark);
......
......@@ -2,7 +2,7 @@ import pytest
def test_inheritance(msg):
from pybind11_tests import Pet, Dog, Rabbit, Hamster, dog_bark, pet_name_species
from pybind11_tests import Pet, Dog, Rabbit, Hamster, Chimera, dog_bark, pet_name_species
roger = Rabbit('Rabbit')
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
......@@ -30,6 +30,10 @@ def test_inheritance(msg):
Invoked with: <m.Pet object at 0>
"""
with pytest.raises(TypeError) as excinfo:
Chimera("lion", "goat")
assert "No constructor defined!" in str(excinfo.value)
def test_automatic_upcasting():
from pybind11_tests import return_class_1, return_class_2, return_class_n, return_none
......
......@@ -81,3 +81,73 @@ test_initializer multiple_inheritance_nonexplicit([](py::module &m) {
m.def("bar_base2a", [](Base2a *b) { return b->bar(); });
m.def("bar_base2a_sharedptr", [](std::shared_ptr<Base2a> b) { return b->bar(); });
});
struct Vanilla {
std::string vanilla() { return "Vanilla"; };
};
struct WithStatic1 {
static std::string static_func1() { return "WithStatic1"; };
static int static_value1;
};
struct WithStatic2 {
static std::string static_func2() { return "WithStatic2"; };
static int static_value2;
};
struct WithDict { };
struct VanillaStaticMix1 : Vanilla, WithStatic1, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix1"; }
static int static_value;
};
struct VanillaStaticMix2 : WithStatic1, Vanilla, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix2"; }
static int static_value;
};
struct VanillaDictMix1 : Vanilla, WithDict { };
struct VanillaDictMix2 : WithDict, Vanilla { };
int WithStatic1::static_value1 = 1;
int WithStatic2::static_value2 = 2;
int VanillaStaticMix1::static_value = 12;
int VanillaStaticMix2::static_value = 12;
test_initializer mi_static_properties([](py::module &pm) {
auto m = pm.def_submodule("mi");
py::class_<Vanilla>(m, "Vanilla")
.def(py::init<>())
.def("vanilla", &Vanilla::vanilla);
py::class_<WithStatic1>(m, "WithStatic1", py::metaclass())
.def(py::init<>())
.def_static("static_func1", &WithStatic1::static_func1)
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
py::class_<WithStatic2>(m, "WithStatic2", py::metaclass())
.def(py::init<>())
.def_static("static_func2", &WithStatic2::static_func2)
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
m, "VanillaStaticMix1", py::metaclass())
.def(py::init<>())
.def_static("static_func", &VanillaStaticMix1::static_func)
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
m, "VanillaStaticMix2", py::metaclass())
.def(py::init<>())
.def_static("static_func", &VanillaStaticMix2::static_func)
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
#if !defined(PYPY_VERSION)
py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>());
py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>());
py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>());
#endif
});
import pytest
def test_multiple_inheritance_cpp():
from pybind11_tests import MIType
......@@ -49,6 +52,17 @@ def test_multiple_inheritance_mix2():
assert mt.bar() == 4
def test_multiple_inheritance_error():
"""Inheriting from multiple C++ bases in Python is not supported"""
from pybind11_tests import Base1, Base2
with pytest.raises(TypeError) as excinfo:
# noinspection PyUnusedLocal
class MI(Base1, Base2):
pass
assert "Can't inherit from multiple C++ classes in Python" in str(excinfo.value)
def test_multiple_inheritance_virtbase():
from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr
......@@ -60,3 +74,38 @@ def test_multiple_inheritance_virtbase():
assert mt.bar() == 4
assert bar_base2a(mt) == 4
assert bar_base2a_sharedptr(mt) == 4
def test_mi_static_properties():
"""Mixing bases with and without static properties should be possible
and the result should be independent of base definition order"""
from pybind11_tests import mi
for d in (mi.VanillaStaticMix1(), mi.VanillaStaticMix2()):
assert d.vanilla() == "Vanilla"
assert d.static_func1() == "WithStatic1"
assert d.static_func2() == "WithStatic2"
assert d.static_func() == d.__class__.__name__
mi.WithStatic1.static_value1 = 1
mi.WithStatic2.static_value2 = 2
assert d.static_value1 == 1
assert d.static_value2 == 2
assert d.static_value == 12
d.static_value1 = 0
assert d.static_value1 == 0
d.static_value2 = 0
assert d.static_value2 == 0
d.static_value = 0
assert d.static_value == 0
@pytest.unsupported_on_pypy
def test_mi_dynamic_attributes():
"""Mixing bases with and without dynamic attribute support"""
from pybind11_tests import mi
for d in (mi.VanillaDictMix1(), mi.VanillaDictMix2()):
d.dynamic = 1
assert d.dynamic == 1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment