WIP: refinement of Python and Ruby support for keyword arguments.

This commit is contained in:
Matthias Koefferlein 2023-12-27 22:56:11 +01:00
parent 8f9b904d87
commit 940ef5319a
6 changed files with 47 additions and 10 deletions

View File

@ -195,7 +195,7 @@ type_to_s (const gsi::ArgType &a, bool for_return)
s += "[]";
break;
case gsi::T_map:
s += "map<";
s += "map<";
if (a.inner_k ()) {
s += type_to_s (*a.inner_k (), false);
}
@ -203,7 +203,7 @@ type_to_s (const gsi::ArgType &a, bool for_return)
if (a.inner ()) {
s += type_to_s (*a.inner (), false);
}
s += "&gt;";
s += ">";
break;
}
if (a.is_cptr () || a.is_ptr ()) {

View File

@ -708,7 +708,7 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args
tl::Variant def_value = a->spec ()->default_value ();
gsi::push_arg (arglist, *a, def_value, &heap);
} else {
throw tl::Exception (tl::to_string ("No argument provided (positional or keyword) and no default value available"));
throw tl::Exception (tl::to_string (tr ("No argument provided (positional or keyword) and no default value available")));
}
} else {
if (iarg >= argc) {
@ -809,6 +809,10 @@ method_adaptor (int mid, PyObject *self, PyObject *args, PyObject *kwargs)
// handle special methods
if (meth->smt () != gsi::MethodBase::None) {
if (kwargs != NULL && PyDict_Size (kwargs) > 0) {
throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted")));
}
ret = special_method_impl (meth->smt (), self, args);
} else {
@ -936,6 +940,10 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args, PyObject *kwargs)
} else {
if (kwargs != NULL && PyDict_Size (kwargs) > 0) {
throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted")));
}
// No action required - the object is default-created later once it is really required.
if (! PyArg_ParseTuple (args, "")) {
return NULL;

View File

@ -176,9 +176,9 @@ static VALUE
get_kwarg (const gsi::ArgType &atype, VALUE kwargs)
{
if (kwargs != Qnil) {
return rb_hash_lookup2 (kwargs, rb_id2sym (rb_intern (atype.spec ()->name ().c_str ())), Qnil);
return rb_hash_lookup2 (kwargs, rb_id2sym (rb_intern (atype.spec ()->name ().c_str ())), Qundef);
} else {
return Qnil;
return Qundef;
}
}
@ -477,7 +477,7 @@ private:
int i = 0;
for (gsi::MethodBase::argument_iterator a = (*m)->begin_arguments (); is_valid && a != (*m)->end_arguments (); ++a, ++i) {
VALUE arg = i >= argc ? get_kwarg (*a, kwargs) : argv[i];
if (arg == Qnil) {
if (arg == Qundef) {
is_valid = a->spec ()->has_default ();
} else if (test_arg (*a, arg, false /*strict*/)) {
++sc;
@ -1060,7 +1060,7 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, i
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a, ++iarg) {
VALUE arg = iarg >= argc ? get_kwarg (*a, kwargs) : argv[iarg];
if (arg == Qnil) {
if (arg == Qundef) {
if (a->spec ()->has_default ()) {
if (kwargs_taken == nkwargs) {
// leave it to the consumer to establish the default values (that is faster)
@ -1069,7 +1069,7 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, i
tl::Variant def_value = a->spec ()->default_value ();
gsi::push_arg (arglist, *a, def_value, &heap);
} else {
throw tl::Exception (tl::to_string ("No argument provided (positional or keyword) and no default value available"));
throw tl::Exception (tl::to_string (tr ("No argument provided (positional or keyword) and no default value available")));
}
} else {
if (iarg >= argc) {
@ -1195,10 +1195,18 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
} else if (meth->smt () != gsi::MethodBase::None) {
if (kwargs != Qnil && rb_hash_size_num (kwargs) > 0) {
throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted")));
}
ret = special_method_impl (meth, argc, argv, self, ctor);
} else if (meth->is_signal ()) {
if (kwargs != Qnil && rb_hash_size_num (kwargs) > 0) {
throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted on events")));
}
if (p) {
static ID id_set = rb_intern ("set");
@ -1245,6 +1253,10 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
// calling an iterator method without block -> deliver an enumerator using "to_enum"
if (kwargs != Qnil && rb_hash_size_num (kwargs) > 0) {
throw tl::Exception (tl::to_string (tr ("Keyword arguments not permitted on enumerators")));
}
static ID id_to_enum = rb_intern ("to_enum");
VALUE method_sym = ID2SYM (rb_intern (meth->primary_name ().c_str ()));

View File

@ -172,6 +172,14 @@ class KWArgsTest(unittest.TestCase):
except Exception as ex:
self.assertEqual(str(ex), "Value cannot be converted to a floating-point value for argument #2 ('dbu') in CplxTrans.to_s")
try:
t = pya.CplxTrans(1.5, 2.5)
tt = pya.CplxTrans()
tt.assign(other = t)
self.assertEqual(True, False)
except Exception as ex:
self.assertEqual(str(ex), "Keyword arguments not permitted in CplxTrans.assign")
try:
t = pya.CplxTrans("17")
self.assertEqual(True, False)

View File

@ -180,6 +180,15 @@ class KWArgs_TestClass < TestBase
assert_equal(ex.to_s, "TypeError: no implicit conversion to float from string for argument #2 ('dbu') in CplxTrans::to_s")
end
begin
t = RBA::CplxTrans::new(1.5, 2.5)
tt = RBA::CplxTrans::new
tt.assign(other: t)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Keyword arguments not permitted in CplxTrans::assign")
end
begin
t = RBA::CplxTrans::new("17")
assert_equal(true, false)

View File

@ -301,10 +301,10 @@ class Tl_TestClass < TestBase
assert_equal(my_recipe.name, "rba_test_recipe")
assert_equal(my_recipe.description, "description")
g = my_recipe.generator("A" => 6, "B" => 7.0)
g = my_recipe.generator({ "A" => 6, "B" => 7.0 })
assert_equal(g, "rba_test_recipe: A=#6,B=##7")
assert_equal("%g" % RBA::Recipe::make(g), "42")
assert_equal("%g" % RBA::Recipe::make(g, "C" => 1.5).to_s, "63")
assert_equal("%g" % RBA::Recipe::make(g, { "C" => 1.5 }).to_s, "63")
my_recipe._destroy
my_recipe = nil