Experimental: implicitly calling constructors from arguments passed tuples or lists for objects - this allows using a (x,y) tuple for Vector or Point arguments

This commit is contained in:
Matthias Koefferlein 2023-11-25 19:06:07 +01:00
parent 5961eab84b
commit b1ddb702b8
2 changed files with 84 additions and 19 deletions

View File

@ -200,7 +200,8 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict)
tl_assert (cls_decl != 0);
int argc = args == NULL ? 0 : int (PyTuple_Size (args));
bool is_tuple = PyTuple_Check (args);
int argc = args == NULL ? 0 : (is_tuple ? int (PyTuple_Size (args)) : int (PyList_Size (args)));
// get number of candidates by argument count
const gsi::MethodBase *meth = 0;
@ -277,9 +278,10 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict)
int sc = 0;
int i = 0;
for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && i < argc && a != (*m)->end_arguments (); ++a, ++i) {
if (test_arg (*a, PyTuple_GetItem (args, i), false /*strict*/)) {
PyObject *arg = is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i);
if (test_arg (*a, arg, false /*strict*/)) {
++sc;
} else if (test_arg (*a, PyTuple_GetItem (args, i), true /*loose*/)) {
} else if (test_arg (*a, arg, true /*loose*/)) {
// non-scoring match
} else {
is_valid = false;
@ -326,7 +328,8 @@ match_method (int mid, PyObject *self, PyObject *args, bool strict)
// one candidate, but needs checking whether compatibility is given - this avoid having to route NotImplemented over TypeError exceptions later
int i = 0;
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) {
if (! test_arg (*a, PyTuple_GetItem (args, i), true /*loose*/)) {
PyObject *arg = is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i);
if (! test_arg (*a, arg, true /*loose*/)) {
return 0;
}
}
@ -607,16 +610,17 @@ special_method_impl (gsi::MethodBase::special_method_type smt, PyObject *self, P
}
}
static void
void
push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, tl::Heap &heap)
{
bool is_tuple = PyTuple_Check (args);
int i = 0;
int argc = args == NULL ? 0 : int (PyTuple_Size (args));
int argc = args == NULL ? 0 : (is_tuple ? int (PyTuple_Size (args)) : int (PyList_Size (args)));
try {
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); i < argc && a != meth->end_arguments (); ++a, ++i) {
push_arg (*a, arglist, PyTuple_GetItem (args, i), heap);
push_arg (*a, arglist, is_tuple ? PyTuple_GetItem (args, i) : PyList_GetItem (args, i), heap);
}
} catch (tl::Exception &ex) {
@ -726,7 +730,7 @@ property_getter_adaptor (int mid, PyObject *self, PyObject *args)
PYA_TRY
int argc = args == NULL ? 0 : int (PyTuple_Size (args));
int argc = args == NULL ? 0 : (PyTuple_Check (args) ? int (PyTuple_Size (args)) : int (PyList_Size (args)));
if (argc != 0) {
throw tl::Exception (tl::to_string (tr ("Property getters must not have an argument")));
}
@ -747,12 +751,12 @@ property_setter_adaptor (int mid, PyObject *self, PyObject *args)
PYA_TRY
int argc = args == NULL ? 0 : int (PyTuple_Size (args));
int argc = args == NULL ? 0 : (PyTuple_Check (args) ? int (PyTuple_Size (args)) : int (PyList_Size (args)));
if (argc != 1) {
throw tl::Exception (tl::to_string (tr ("Property setter needs exactly one argument")));
}
PyObject *value = PyTuple_GetItem (args, 0);
PyObject *value = PyTuple_Check (args) ? PyTuple_GetItem (args, 0) : PyList_GetItem (args, 0);
if (value) {
ret = property_setter_impl (mid, self, value);
}
@ -777,7 +781,8 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args)
p->destroy ();
}
const gsi::MethodBase *meth = match_method (mid, self, args, PyTuple_Size (args) > 0 || ! p->cls_decl ()->can_default_create ());
int argc = PyTuple_Check (args) ? int (PyTuple_Size (args)) : int (PyList_Size (args));
const gsi::MethodBase *meth = match_method (mid, self, args, argc > 0 || ! p->cls_decl ()->can_default_create ());
if (meth && meth->smt () == gsi::MethodBase::None) {

View File

@ -32,6 +32,8 @@
namespace pya
{
void push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args, tl::Heap &heap);
// -------------------------------------------------------------------
// Serialization adaptors for strings, variants, vectors and maps
@ -454,6 +456,8 @@ struct writer<gsi::ObjectType>
{
void operator() (gsi::SerialArgs *aa, PyObject *arg, const gsi::ArgType &atype, tl::Heap *heap)
{
const gsi::ClassBase *acls = atype.cls ();
if (arg == Py_None || arg == NULL) {
if (! (atype.is_ptr () || atype.is_cptr ())) {
@ -465,14 +469,50 @@ struct writer<gsi::ObjectType>
}
if (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ()) {
if (PyTuple_Check (arg) || PyList_Check (arg)) {
// we may implicitly convert a tuple into a constructor call of a target object -
// for now we only check whether the number of arguments is compatible with the list given.
int n = PyTuple_Check (arg) ? int (PyTuple_Size (arg)) : int (PyList_Size (arg));
const gsi::MethodBase *meth = 0;
for (gsi::ClassBase::method_iterator c = acls->begin_constructors (); c != acls->end_constructors (); ++c) {
if ((*c)->compatible_with_num_args (n)) {
meth = *c;
break;
}
}
if (!meth) {
throw tl::Exception (tl::to_string (tr ("No constructor of %s available that takes %d arguments (implicit call from tuple)")), acls->name (), n);
}
// implicit call of constructor
gsi::SerialArgs retlist (meth->retsize ());
gsi::SerialArgs arglist (meth->argsize ());
push_args (arglist, meth, arg, *heap);
meth->call (0, arglist, retlist);
void *new_obj = retlist.read<void *> (*heap);
if (new_obj && (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ())) {
// For pointers or refs, ownership over these objects is not transferred.
// Hence we have to keep them on the heap.
// TODO: what if the called method takes ownership using keep()?
heap->push (new gsi::ObjectHolder (acls, new_obj));
}
aa->write<void *> (new_obj);
} else if (atype.is_ptr () || atype.is_cptr () || atype.is_ref () || atype.is_cref ()) {
const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg));
if (! cls_decl) {
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
}
if (cls_decl->is_derived_from (atype.cls ())) {
if (cls_decl->is_derived_from (acls)) {
PYAObjectBase *p = PYAObjectBase::from_pyobject (arg);
@ -483,14 +523,15 @@ struct writer<gsi::ObjectType>
aa->write<void *> (p->obj ());
}
} else if (cls_decl->can_convert_to (atype.cls ())) {
} else if (cls_decl->can_convert_to (acls)) {
PYAObjectBase *p = PYAObjectBase::from_pyobject (arg);
// We can convert objects for cref and cptr, but ownership over these objects is not transferred.
// Hence we have to keep them on the heap.
void *new_obj = atype.cls ()->create_obj_from (p->cls_decl (), p->obj ());
heap->push (new gsi::ObjectHolder (atype.cls (), new_obj));
// TODO: what if the called method takes ownership using keep()?
void *new_obj = acls->create_obj_from (p->cls_decl (), p->obj ());
heap->push (new gsi::ObjectHolder (acls, new_obj));
aa->write<void *> (new_obj);
} else {
@ -504,7 +545,7 @@ struct writer<gsi::ObjectType>
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
}
if (cls_decl->is_derived_from (atype.cls ())) {
if (cls_decl->is_derived_from (acls)) {
PYAObjectBase *p = PYAObjectBase::from_pyobject (arg);
@ -515,7 +556,7 @@ struct writer<gsi::ObjectType>
aa->write<void *> (atype.cls ()->clone (p->obj ()));
}
} else if (cls_decl->can_convert_to (atype.cls ())) {
} else if (cls_decl->can_convert_to (acls)) {
PYAObjectBase *p = PYAObjectBase::from_pyobject (arg);
aa->write<void *> (atype.cls ()->create_obj_from (cls_decl, p->obj ()));
@ -1141,19 +1182,38 @@ struct test_arg_func<gsi::ObjectType>
{
void operator() (bool *ret, PyObject *arg, const gsi::ArgType &atype, bool loose)
{
const gsi::ClassBase *acls = atype.cls ();
// for const X * or X *, null is an allowed value
if ((atype.is_cptr () || atype.is_ptr ()) && arg == Py_None) {
*ret = true;
return;
}
if (loose && (PyTuple_Check (arg) || PyList_Check (arg))) {
// we may implicitly convert a tuple into a constructor call of a target object -
// for now we only check whether the number of arguments is compatible with the list given.
int n = PyTuple_Check (arg) ? int (PyTuple_Size (arg)) : int (PyList_Size (arg));
*ret = false;
for (gsi::ClassBase::method_iterator c = acls->begin_constructors (); c != acls->end_constructors (); ++c) {
if ((*c)->compatible_with_num_args (n)) {
*ret = true;
break;
}
}
return;
}
const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg));
if (! cls_decl) {
*ret = false;
return;
}
if (! (cls_decl == atype.cls () || (loose && (cls_decl->is_derived_from (atype.cls ()) || cls_decl->can_convert_to(atype.cls ()))))) {
if (! (cls_decl == acls || (loose && (cls_decl->is_derived_from (atype.cls ()) || cls_decl->can_convert_to (atype.cls ()))))) {
*ret = false;
return;
}