Providing a different implementation of the multi-base mixin concept for Python to support multiple bases in Qt binding (e.g. QIODeviceBase)

This commit is contained in:
Matthias Koefferlein 2021-12-10 00:53:51 +01:00
parent 202ac55200
commit f15db66fc4
5 changed files with 347 additions and 332 deletions

View File

@ -116,6 +116,44 @@ public:
MethodTable (const gsi::ClassBase *cls_decl) MethodTable (const gsi::ClassBase *cls_decl)
: m_method_offset (0), m_property_offset (0), mp_cls_decl (cls_decl) : m_method_offset (0), m_property_offset (0), mp_cls_decl (cls_decl)
{ {
// signals are translated into the setters and getters
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if ((*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
add_getter (syn->name, *m);
add_setter (syn->name, *m);
}
}
}
// first add getters and setters
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if (! (*m)->is_callback ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (syn->is_getter) {
add_getter (syn->name, *m);
} else if (syn->is_setter) {
add_setter (syn->name, *m);
}
}
}
}
// then add normal methods - on name clash with properties make them a getter
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if (! (*m)->is_callback () && ! (*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (! syn->is_getter && ! syn->is_setter) {
if ((*m)->end_arguments () - (*m)->begin_arguments () == 0 && find_property ((*m)->is_static (), syn->name).first) {
add_getter (syn->name, *m);
} else {
add_method (syn->name, *m);
}
}
}
}
}
if (cls_decl->base ()) { if (cls_decl->base ()) {
const MethodTable *base_mt = method_table_by_class (cls_decl->base ()); const MethodTable *base_mt = method_table_by_class (cls_decl->base ());
tl_assert (base_mt); tl_assert (base_mt);
@ -369,24 +407,34 @@ private:
struct PythonClassClientData struct PythonClassClientData
: public gsi::PerClassClientSpecificData : public gsi::PerClassClientSpecificData
{ {
PythonClassClientData (const gsi::ClassBase *_cls, PyTypeObject *_py_type) PythonClassClientData (const gsi::ClassBase *_cls, PyTypeObject *_py_type, PyTypeObject *_py_type_static)
: py_type_object (_py_type), method_table (_cls) : py_type_object (_py_type), py_type_object_static (_py_type_static), method_table (_cls)
{ {
// .. nothing yet .. // .. nothing yet ..
} }
PyTypeObject *py_type_object; PyTypeObject *py_type_object;
PyTypeObject *py_type_object_static;
MethodTable method_table; MethodTable method_table;
static PyTypeObject *py_type (const gsi::ClassBase &cls_decl) static PyTypeObject *py_type (const gsi::ClassBase &cls_decl, bool as_static)
{ {
PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl.data (gsi::ClientIndex::Python)); PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl.data (gsi::ClientIndex::Python));
return cd ? cd->py_type_object : 0; return cd ? (as_static ? cd->py_type_object_static : cd->py_type_object) : 0;
} }
static void initialize (const gsi::ClassBase &cls_decl, PyTypeObject *py_type) static void initialize (const gsi::ClassBase &cls_decl, PyTypeObject *py_type, bool as_static)
{ {
cls_decl.set_data (gsi::ClientIndex::Python, new PythonClassClientData (&cls_decl, py_type)); PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl.data (gsi::ClientIndex::Python));
if (cd) {
if (as_static) {
cd->py_type_object_static = py_type;
} else {
cd->py_type_object = py_type;
}
} else {
cls_decl.set_data (gsi::ClientIndex::Python, new PythonClassClientData (&cls_decl, as_static ? NULL : py_type, as_static ? py_type : NULL));
}
} }
}; };
@ -2224,7 +2272,6 @@ PythonModule::init (const char *mod_name, const char *description)
// do some checks before we create the module // do some checks before we create the module
tl_assert (mod_name != 0); tl_assert (mod_name != 0);
tl_assert (mp_module.get () == 0); tl_assert (mp_module.get () == 0);
check (mod_name);
m_mod_name = pymod_name + "." + mod_name; m_mod_name = pymod_name + "." + mod_name;
m_mod_description = description; m_mod_description = description;
@ -2269,7 +2316,6 @@ PythonModule::init (const char *mod_name, PyObject *module)
{ {
// do some checks before we create the module // do some checks before we create the module
tl_assert (mp_module.get () == 0); tl_assert (mp_module.get () == 0);
check (mod_name);
m_mod_name = mod_name; m_mod_name = mod_name;
mp_module = PythonRef (module); mp_module = PythonRef (module);
@ -2325,41 +2371,6 @@ PythonModule::python_doc (const gsi::MethodBase *method)
} }
} }
void
PythonModule::check (const char *mod_name)
{
if (! mod_name) {
return;
}
// Check whether the new classes are self-contained within this module
for (gsi::ClassBase::class_iterator c = gsi::ClassBase::begin_classes (); c != gsi::ClassBase::end_classes (); ++c) {
if (c->module () != mod_name) {
// don't handle classes outside this module
continue;
}
if (PythonClassClientData::py_type (*c)) {
// don't handle classes twice
continue;
}
// All child classes must originate from this module or be known already
for (tl::weak_collection<gsi::ClassBase>::const_iterator cc = c->begin_child_classes (); cc != c->end_child_classes (); ++cc) {
if (! PythonClassClientData::py_type (*cc->declaration ()) && cc->module () != mod_name) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Class %s from module %s depends on %s.%s (try 'import %s' before 'import %s')")), c->name (), mod_name, cc->module (), cc->name (), pymod_name + "." + cc->module (), pymod_name + "." + mod_name));
}
}
// Same for base class
if (c->base () && ! PythonClassClientData::py_type (*c->base ()) && c->base ()->module () != mod_name) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Class %s from module %s depends on %s.%s (try 'import %s' before 'import %s')")), c->name (), mod_name, c->base ()->module (), c->base ()->name (), pymod_name + "." + c->base ()->module (), pymod_name + "." + mod_name));
}
}
}
namespace namespace
{ {
@ -2380,7 +2391,6 @@ public:
// got an extension // got an extension
tl_assert (cls->parent ()); tl_assert (cls->parent ());
m_extensions_for [cls->parent ()->declaration ()].push_back (cls->declaration ()); m_extensions_for [cls->parent ()->declaration ()].push_back (cls->declaration ());
m_extensions.insert (cls->declaration ());
} else { } else {
@ -2389,9 +2399,15 @@ public:
} }
} }
PyTypeObject *make_class (const gsi::ClassBase *cls) PyTypeObject *make_class (const gsi::ClassBase *cls, bool as_static)
{ {
PyTypeObject *pt = PythonClassClientData::py_type (*cls); // NOTE: with as_static = true, this method produces a mixin. This is a class entirely consisting
// of static constants and child classes only. It can be mixed into an existing class for emulation
// additional base classes.
// Everything else, like properties and methods will not work as the method enumeration scheme is
// not capable of handling more than a single base class.
PyTypeObject *pt = PythonClassClientData::py_type (*cls, as_static);
if (pt != 0) { if (pt != 0) {
return pt; return pt;
} }
@ -2403,28 +2419,26 @@ public:
int n_bases = (cls->base () != 0 ? 1 : 0); int n_bases = (cls->base () != 0 ? 1 : 0);
auto exts = m_extensions_for.find (cls); auto exts = m_extensions_for.find (cls);
if (exts != m_extensions_for.end ()) { if (exts != m_extensions_for.end ()) {
// @@@ n_bases += int (exts->second.size ()); n_bases += int (exts->second.size ());
} }
bases = PythonRef (PyTuple_New (n_bases)); bases = PythonRef (PyTuple_New (n_bases));
int ibase = 0; int ibase = 0;
if (cls->base () != 0) { if (cls->base () != 0) {
PyTypeObject *pt = make_class (cls->base ()); PyTypeObject *pt = make_class (cls->base (), as_static);
PyObject *base = (PyObject *) pt; PyObject *base = (PyObject *) pt;
Py_INCREF (base); Py_INCREF (base);
PyTuple_SetItem (bases.get (), ibase++, base); PyTuple_SetItem (bases.get (), ibase++, base);
} }
/*@@@
if (exts != m_extensions_for.end ()) { if (exts != m_extensions_for.end ()) {
for (auto ie = exts->second.begin (); ie != exts->second.end (); ++ie) { for (auto ie = exts->second.begin (); ie != exts->second.end (); ++ie) {
PyObject *base = (PyObject *) make_class (*ie); PyObject *base = (PyObject *) make_class (*ie, true);
Py_INCREF (base); Py_INCREF (base);
PyTuple_SetItem (bases.get (), ibase++, base); PyTuple_SetItem (bases.get (), ibase++, base);
} }
} }
@@@*/
// creates the type object // creates the type object
@ -2434,7 +2448,11 @@ public:
PyDict_SetItemString (dict.get (), "__gsi_id__", PythonRef (c2python (mp_module->next_class_id ())).get ()); PyDict_SetItemString (dict.get (), "__gsi_id__", PythonRef (c2python (mp_module->next_class_id ())).get ());
PythonRef args (PyTuple_New (3)); PythonRef args (PyTuple_New (3));
if (! as_static) {
PyTuple_SetItem (args.get (), 0, c2python (cls->name ())); PyTuple_SetItem (args.get (), 0, c2python (cls->name ()));
} else {
PyTuple_SetItem (args.get (), 0, c2python (cls->name () + "_Mixin"));
}
PyTuple_SetItem (args.get (), 1, bases.release ()); PyTuple_SetItem (args.get (), 1, bases.release ());
PyTuple_SetItem (args.get (), 2, dict.release ()); PyTuple_SetItem (args.get (), 2, dict.release ());
@ -2445,37 +2463,42 @@ public:
} }
// Customize // Customize
if (! as_static) {
type->tp_basicsize += sizeof (PYAObjectBase); type->tp_basicsize += sizeof (PYAObjectBase);
type->tp_init = &pya_object_init; type->tp_init = &pya_object_init;
type->tp_new = &pya_object_new; type->tp_new = &pya_object_new;
type->tp_dealloc = (destructor) &pya_object_deallocate; type->tp_dealloc = (destructor) &pya_object_deallocate;
type->tp_setattro = PyObject_GenericSetAttr; type->tp_setattro = PyObject_GenericSetAttr;
type->tp_getattro = PyObject_GenericGetAttr; type->tp_getattro = PyObject_GenericGetAttr;
}
PythonClassClientData::initialize (*cls, type); PythonClassClientData::initialize (*cls, type, as_static);
mp_module->register_class (cls); mp_module->register_class (cls);
tl_assert (mp_module->cls_for_type (type) == cls); tl_assert (mp_module->cls_for_type (type) == cls);
// Add to the parent class as child class or add to module // add to the parent class as child class or add to module
if (cls->parent ()) { if (! cls->parent ()) {
tl_assert (cls->parent ()->declaration () != 0);
PyTypeObject *parent_type = make_class (cls->parent ()->declaration ());
PythonRef attr ((PyObject *) type);
set_type_attr (parent_type, cls->name ().c_str (), attr);
} else {
PyList_Append (m_all_list, PythonRef (c2python (cls->name ())).get ()); PyList_Append (m_all_list, PythonRef (c2python (cls->name ())).get ());
PyModule_AddObject (mp_module->module (), cls->name ().c_str (), (PyObject *) type); PyModule_AddObject (mp_module->module (), cls->name ().c_str (), (PyObject *) type);
} }
// produce the child classes
for (auto cc = cls->begin_child_classes (); cc != cls->end_child_classes (); ++cc) {
PyTypeObject *child_class = make_class (cc.operator-> (), as_static);
PythonRef attr ((PyObject *) child_class, false /*borrowed*/);
set_type_attr (type, cc->name ().c_str (), attr);
}
// add named extensions // add named extensions
auto links = m_links_for.find (cls); auto links = m_links_for.find (cls);
if (links != m_links_for.end ()) { if (links != m_links_for.end ()) {
for (auto il = links->second.begin (); il != links->second.end (); ++il) { for (auto il = links->second.begin (); il != links->second.end (); ++il) {
PyTypeObject *linked_type = make_class (il->second); PyTypeObject *linked_type = make_class (il->second, false);
PythonRef attr ((PyObject *) linked_type, false /*borrowed*/); PythonRef attr ((PyObject *) linked_type, false /*borrowed*/);
set_type_attr (type, il->first.c_str (), attr); set_type_attr (type, il->first.c_str (), attr);
} }
@ -2485,46 +2508,10 @@ public:
MethodTable *mt = MethodTable::method_table_by_class (cls); MethodTable *mt = MethodTable::method_table_by_class (cls);
// signals are translated into the setters and getters
for (gsi::ClassBase::method_iterator m = cls->begin_methods (); m != cls->end_methods (); ++m) {
if ((*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
mt->add_getter (syn->name, *m);
mt->add_setter (syn->name, *m);
}
}
}
// first add getters and setters
for (gsi::ClassBase::method_iterator m = cls->begin_methods (); m != cls->end_methods (); ++m) {
if (! (*m)->is_callback ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (syn->is_getter) {
mt->add_getter (syn->name, *m);
} else if (syn->is_setter) {
mt->add_setter (syn->name, *m);
}
}
}
}
// then add normal methods - on name clash with properties make them a getter
for (gsi::ClassBase::method_iterator m = cls->begin_methods (); m != cls->end_methods (); ++m) {
if (! (*m)->is_callback () && ! (*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (! syn->is_getter && ! syn->is_setter) {
if ((*m)->end_arguments () - (*m)->begin_arguments () == 0 && mt->find_property ((*m)->is_static (), syn->name).first) {
mt->add_getter (syn->name, *m);
} else {
mt->add_method (syn->name, *m);
}
}
}
}
}
// produce the properties // produce the properties
if (! as_static) {
for (size_t mid = mt->bottom_property_mid (); mid < mt->top_property_mid (); ++mid) { for (size_t mid = mt->bottom_property_mid (); mid < mt->top_property_mid (); ++mid) {
MethodTableEntry::method_iterator begin_setters = mt->begin_setters (mid); MethodTableEntry::method_iterator begin_setters = mt->begin_setters (mid);
@ -2614,6 +2601,8 @@ public:
} }
}
// collect the names which have been disambiguated static/non-static wise // collect the names which have been disambiguated static/non-static wise
std::vector<std::string> disambiguated_names; std::vector<std::string> disambiguated_names;
@ -2691,6 +2680,8 @@ public:
tl_assert (mid < sizeof (method_adaptors) / sizeof (method_adaptors[0])); tl_assert (mid < sizeof (method_adaptors) / sizeof (method_adaptors[0]));
if (! mt->is_static (mid)) { if (! mt->is_static (mid)) {
if (! as_static) {
std::vector<std::string> alt_names; std::vector<std::string> alt_names;
if (name == "to_s" && m_first->compatible_with_num_args (0)) { if (name == "to_s" && m_first->compatible_with_num_args (0)) {
@ -2765,6 +2756,8 @@ public:
PythonRef attr = PythonRef (PyDescr_NewMethod (type, method)); PythonRef attr = PythonRef (PyDescr_NewMethod (type, method));
set_type_attr (type, name, attr); set_type_attr (type, name, attr);
}
} else if (isupper (name [0]) || m_first->is_const ()) { } else if (isupper (name [0]) || m_first->is_const ()) {
if ((mt->end (mid) - mt->begin (mid)) == 1 && m_first->begin_arguments () == m_first->end_arguments ()) { if ((mt->end (mid) - mt->begin (mid)) == 1 && m_first->begin_arguments () == m_first->end_arguments ()) {
@ -2782,7 +2775,7 @@ public:
mp_module->add_python_doc (*cls, mt, int (mid), tl::to_string (tr ("This attribute is not available for Python"))); mp_module->add_python_doc (*cls, mt, int (mid), tl::to_string (tr ("This attribute is not available for Python")));
} }
} else { } else if (! as_static) {
if (m_first->ret_type ().type () == gsi::T_object && m_first->ret_type ().pass_obj () && name == "new") { if (m_first->ret_type ().type () == gsi::T_object && m_first->ret_type ().pass_obj () && name == "new") {
@ -2813,7 +2806,7 @@ public:
} }
} if (! as_static) {
// Complete the comparison operators if necessary. // Complete the comparison operators if necessary.
// Unlike Ruby, Python does not automatically implement != from == for example. // Unlike Ruby, Python does not automatically implement != from == for example.
@ -2890,6 +2883,8 @@ public:
} }
}
// install the static/non-static dispatcher descriptor // install the static/non-static dispatcher descriptor
for (std::vector<std::string>::const_iterator a = disambiguated_names.begin (); a != disambiguated_names.end (); ++a) { for (std::vector<std::string>::const_iterator a = disambiguated_names.begin (); a != disambiguated_names.end (); ++a) {
@ -2898,13 +2893,11 @@ public:
PyObject *attr_class = PyObject_GetAttrString ((PyObject *) type, ("_class_" + *a).c_str ()); PyObject *attr_class = PyObject_GetAttrString ((PyObject *) type, ("_class_" + *a).c_str ());
if (attr_inst == NULL || attr_class == NULL) { if (attr_inst == NULL || attr_class == NULL) {
// some error -> don't install the disambiguator // some error or disambiguator is not required -> don't install it
Py_XDECREF (attr_inst); Py_XDECREF (attr_inst);
Py_XDECREF (attr_class); Py_XDECREF (attr_class);
PyErr_Clear (); PyErr_Clear ();
tl::warn << "Unable to install a static/non-static disambiguator for " << *a << " in class " << cls->name ();
} else { } else {
PyObject *desc = PYAAmbiguousMethodDispatcher::create (attr_inst, attr_class); PyObject *desc = PYAAmbiguousMethodDispatcher::create (attr_inst, attr_class);
@ -2916,6 +2909,8 @@ public:
} }
}
mt->finish (); mt->finish ();
return type; return type;
@ -2925,7 +2920,6 @@ private:
PythonModule *mp_module; PythonModule *mp_module;
PyObject *m_all_list; PyObject *m_all_list;
std::map<const gsi::ClassBase *, std::vector<const gsi::ClassBase *> > m_extensions_for; std::map<const gsi::ClassBase *, std::vector<const gsi::ClassBase *> > m_extensions_for;
std::set<const gsi::ClassBase *> m_extensions;
std::map<const gsi::ClassBase *, std::vector<std::pair<std::string, const gsi::ClassBase *> > > m_links_for; std::map<const gsi::ClassBase *, std::vector<std::pair<std::string, const gsi::ClassBase *> > > m_links_for;
}; };
@ -2968,16 +2962,16 @@ PythonModule::make_classes (const char *mod_name)
for (std::list<const gsi::ClassBase *>::const_iterator c = sorted_classes.begin (); c != sorted_classes.end (); ++c) { for (std::list<const gsi::ClassBase *>::const_iterator c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if ((*c)->module () != mod_name) { if ((*c)->module () != mod_name) {
// don't handle classes outside this module, but require them to be present // don't handle classes outside this module, but require them to be present
if (! PythonClassClientData::py_type (**c)) { if (! PythonClassClientData::py_type (**c, false)) {
throw tl::Exception (tl::sprintf ("class %s.%s required from outside the module %s, but that module is not loaded", (*c)->module (), (*c)->name (), mod_name)); throw tl::Exception (tl::sprintf ("class %s.%s required from outside the module %s, but that module is not loaded", (*c)->module (), (*c)->name (), mod_name));
} }
} }
} }
} }
// first pass: register the extensions // first pass: register the extensions using all available classes
for (auto c = sorted_classes.begin (); c != sorted_classes.end (); ++c) { for (std::list<const gsi::ClassBase *>::const_iterator c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if ((*c)->declaration () != *c && (! mod_name || (*c)->module () == mod_name)) { if ((*c)->declaration () != *c) {
gen.register_extension (*c); gen.register_extension (*c);
} }
} }
@ -2985,7 +2979,7 @@ PythonModule::make_classes (const char *mod_name)
// second pass: make the classes // second pass: make the classes
for (auto c = sorted_classes.begin (); c != sorted_classes.end (); ++c) { for (auto c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if ((*c)->declaration () == *c && (! mod_name || (*c)->module () == mod_name)) { if ((*c)->declaration () == *c && (! mod_name || (*c)->module () == mod_name)) {
gen.make_class (*c); gen.make_class (*c, false);
} }
} }
} }
@ -3010,7 +3004,7 @@ const gsi::ClassBase *PythonModule::cls_for_type (PyTypeObject *type)
PyTypeObject *PythonModule::type_for_cls (const gsi::ClassBase *cls) PyTypeObject *PythonModule::type_for_cls (const gsi::ClassBase *cls)
{ {
return PythonClassClientData::py_type (*cls); return PythonClassClientData::py_type (*cls, false);
} }
} }

View File

@ -158,8 +158,6 @@ public:
} }
private: private:
static void check (const char *mod_name);
std::list<std::string> m_string_heap; std::list<std::string> m_string_heap;
std::vector<PyMethodDef *> m_methods_heap; std::vector<PyMethodDef *> m_methods_heap;
std::vector<PyGetSetDef *> m_getseters_heap; std::vector<PyGetSetDef *> m_getseters_heap;

View File

@ -586,5 +586,17 @@ PYAObjectBase::obj ()
return m_obj; return m_obj;
} }
PYAObjectBase *
PYAObjectBase::from_pyobject (PyObject *py_object)
{
if (Py_TYPE (py_object)->tp_init == NULL) {
throw tl::Exception (tl::to_string (tr ("Extension classes do not support instance methods or properties")));
}
PYAObjectBase *pya_object = from_pyobject_unsafe (py_object);
tl_assert (pya_object->py_object () == py_object);
return pya_object;
}
} }

View File

@ -70,25 +70,21 @@ public:
*/ */
~PYAObjectBase (); ~PYAObjectBase ();
/**
* @brief Gets the PYAObjectBase pointer from a PyObject pointer
*/
static PYAObjectBase *from_pyobject (PyObject *py_object)
{
PYAObjectBase *pya_object = (PYAObjectBase *)((char *) py_object + Py_TYPE (py_object)->tp_basicsize - sizeof (PYAObjectBase));
tl_assert (pya_object->py_object () == py_object);
return pya_object;
}
/** /**
* @brief Gets the PYAObjectBase pointer from a PyObject pointer * @brief Gets the PYAObjectBase pointer from a PyObject pointer
* This version doesn't check anything. * This version doesn't check anything.
*/ */
static PYAObjectBase *from_pyobject_unsafe (PyObject *py_object) static PYAObjectBase *from_pyobject_unsafe (PyObject *py_object)
{ {
// the objects must not be a pure static extension
return (PYAObjectBase *)((char *) py_object + Py_TYPE (py_object)->tp_basicsize - sizeof (PYAObjectBase)); return (PYAObjectBase *)((char *) py_object + Py_TYPE (py_object)->tp_basicsize - sizeof (PYAObjectBase));
} }
/**
* @brief Gets the PYAObjectBase pointer from a PyObject pointer
*/
static PYAObjectBase *from_pyobject (PyObject *py_object);
/** /**
* @brief Indicates that a C++ object is present * @brief Indicates that a C++ object is present
*/ */

View File

@ -3045,8 +3045,23 @@ class BasicTest(unittest.TestCase):
self.assertEqual(pya.A.ba_to_ia(b'\x00\x01\x02'), [ 0, 1, 2 ]) self.assertEqual(pya.A.ba_to_ia(b'\x00\x01\x02'), [ 0, 1, 2 ])
# Tests multi-base mixins (only constants and enums available)
def test_multiBaseMixins(self):
bb = pya.BB() # base classes B1,B2,B3
bb.set1(17) # B1
self.assertEqual(bb.get1(), 17) # B1
bb.set1(21) # B1
self.assertEqual(bb.get1(), 21) # B1
self.assertEqual(pya.BB.C2, 17) # B2
self.assertEqual(pya.BB.C3, -1) # B3
self.assertEqual(pya.BB.E.E3B.to_i(), 101) # B3
self.assertEqual(bb.d3(pya.BB.E.E3C, pya.BB.E.E3A), -2) # BB with B3 enums
self.assertEqual(bb.d3(pya.BB.E.E3A, pya.BB.E.E3C), 2) # BB with B3 enums
# Custom factory implemented in Python # Custom factory implemented in Python
#
def test_80(self): def test_80(self):
gc = pya.GObject.g_inst_count() gc = pya.GObject.g_inst_count()