Allow py::arg().none(false) argument attribute

This attribute lets you disable (or explicitly enable) passing None to
an argument that otherwise would allow it by accepting
a value by raw pointer or shared_ptr.
This commit is contained in:
Jason Rhinelander
2017-05-17 11:55:43 -04:00
parent 813d7e8687
commit 4e1e4a580e
6 changed files with 139 additions and 14 deletions

View File

@@ -123,9 +123,10 @@ struct argument_record {
const char *descr; ///< Human-readable version of the argument value
handle value; ///< Associated Python object
bool convert : 1; ///< True if the argument is allowed to convert when loading
bool none : 1; ///< True if None is allowed when loading
argument_record(const char *name, const char *descr, handle value, bool convert)
: name(name), descr(descr), value(value), convert(convert) { }
argument_record(const char *name, const char *descr, handle value, bool convert, bool none)
: name(name), descr(descr), value(value), convert(convert), none(none) { }
};
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
@@ -338,8 +339,8 @@ template <> struct process_attribute<is_operator> : process_attribute_default<is
template <> struct process_attribute<arg> : process_attribute_default<arg> {
static void init(const arg &a, function_record *r) {
if (r->is_method && r->args.empty())
r->args.emplace_back("self", nullptr, handle(), true /*convert*/);
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert);
r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/);
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
}
};
@@ -347,7 +348,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
static void init(const arg_v &a, function_record *r) {
if (r->is_method && r->args.empty())
r->args.emplace_back("self", nullptr /*descr*/, handle() /*parent*/, true /*convert*/);
r->args.emplace_back("self", nullptr /*descr*/, handle() /*parent*/, true /*convert*/, false /*none not allowed*/);
if (!a.value) {
#if !defined(NDEBUG)
@@ -370,7 +371,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
"Compile in debug mode for more information.");
#endif
}
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert);
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none);
}
};

View File

@@ -1381,14 +1381,17 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
/// Annotation for arguments
struct arg {
/// Constructs an argument with the name of the argument; if null or omitted, this is a positional argument.
constexpr explicit arg(const char *name = nullptr) : name(name), flag_noconvert(false) { }
constexpr explicit arg(const char *name = nullptr) : name(name), flag_noconvert(false), flag_none(true) { }
/// Assign a value to this argument
template <typename T> arg_v operator=(T &&value) const;
/// Indicate that the type should not be converted in the type caster
arg &noconvert(bool flag = true) { flag_noconvert = flag; return *this; }
/// Indicates that the argument should/shouldn't allow None (e.g. for nullable pointer args)
arg &none(bool flag = true) { flag_none = flag; return *this; }
const char *name; ///< If non-null, this is a named kwargs argument
bool flag_noconvert : 1; ///< If set, do not allow conversion (requires a supporting type caster!)
bool flag_none : 1; ///< If set (the default), allow None to be passed to this argument
};
/// \ingroup annotations
@@ -1421,6 +1424,9 @@ public:
/// Same as `arg::noconvert()`, but returns *this as arg_v&, not arg&
arg_v &noconvert(bool flag = true) { arg::noconvert(flag); return *this; }
/// Same as `arg::nonone()`, but returns *this as arg_v&, not arg&
arg_v &none(bool flag = true) { arg::none(flag); return *this; }
/// The default value
object value;
/// The (optional) description of the default value

View File

@@ -466,18 +466,23 @@ protected:
size_t args_copied = 0;
// 1. Copy any position arguments given.
bool bad_kwarg = false;
bool bad_arg = false;
for (; args_copied < args_to_copy; ++args_copied) {
if (kwargs_in && args_copied < func.args.size() && func.args[args_copied].name
&& PyDict_GetItemString(kwargs_in, func.args[args_copied].name)) {
bad_kwarg = true;
argument_record *arg_rec = args_copied < func.args.size() ? &func.args[args_copied] : nullptr;
if (kwargs_in && arg_rec && arg_rec->name && PyDict_GetItemString(kwargs_in, arg_rec->name)) {
bad_arg = true;
break;
}
call.args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
call.args_convert.push_back(args_copied < func.args.size() ? func.args[args_copied].convert : true);
handle arg(PyTuple_GET_ITEM(args_in, args_copied));
if (arg_rec && !arg_rec->none && arg.is_none()) {
bad_arg = true;
break;
}
call.args.push_back(arg);
call.args_convert.push_back(arg_rec ? arg_rec->convert : true);
}
if (bad_kwarg)
if (bad_arg)
continue; // Maybe it was meant for another overload (issue #688)
// We'll need to copy this if we steal some kwargs for defaults