Defer None loading to second pass

Many of our `is_none()` checks in type caster loading return true, but
this should really be considered a deferral so that, for example, an
overload with a `py::none` argument would win over one that takes
`py::none` as a null option.

This keeps None-accepting for the `!convert` pass only for std::optional
and void casters.  (The `char` caster already deferred None; this just
extends that behaviour to other casters).
This commit is contained in:
Jason Rhinelander 2017-05-09 13:08:04 -04:00
parent 7fb01ecd9c
commit 93e3eac6f9
4 changed files with 35 additions and 2 deletions

View File

@ -218,6 +218,8 @@ public:
if (!src || !typeinfo)
return false;
if (src.is_none()) {
// Defer accepting None to other overloads (if we aren't in convert mode):
if (!convert) return false;
value = nullptr;
return true;
}
@ -982,6 +984,8 @@ public:
if (!src || !typeinfo)
return false;
if (src.is_none()) {
// Defer accepting None to other overloads (if we aren't in convert mode):
if (!convert) return false;
value = nullptr;
return true;
}

View File

@ -22,9 +22,12 @@ struct type_caster<std::function<Return(Args...)>> {
using function_type = Return (*) (Args...);
public:
bool load(handle src, bool) {
if (src.is_none())
bool load(handle src, bool convert) {
if (src.is_none()) {
// Defer accepting None to other overloads (if we aren't in convert mode):
if (!convert) return false;
return true;
}
if (!isinstance<function>(src))
return false;

View File

@ -500,6 +500,18 @@ test_initializer python_types([](py::module &m) {
m.def("return_none_int", []() -> int * { return nullptr; });
m.def("return_none_float", []() -> float * { return nullptr; });
m.def("defer_none_cstring", [](char *) { return false; });
m.def("defer_none_cstring", [](py::none) { return true; });
m.def("defer_none_custom", [](ExamplePythonTypes *) { return false; });
m.def("defer_none_custom", [](py::none) { return true; });
// void and optional, however, don't defer:
m.def("nodefer_none_void", [](void *) { return true; });
m.def("nodefer_none_void", [](py::none) { return false; });
#ifdef PYBIND11_HAS_OPTIONAL
m.def("nodefer_none_optional", [](std::optional<int>) { return true; });
m.def("nodefer_none_optional", [](py::none) { return false; });
#endif
m.def("return_capsule_with_destructor",
[]() {
py::print("creating capsule");

View File

@ -550,8 +550,22 @@ def test_builtins_cast_return_none():
assert m.return_none_float() is None
def test_none_deferred():
"""None passed as various argument types should defer to other overloads"""
import pybind11_tests as m
assert not m.defer_none_cstring("abc")
assert m.defer_none_cstring(None)
assert not m.defer_none_custom(m.ExamplePythonTypes.new_instance())
assert m.defer_none_custom(None)
assert m.nodefer_none_void(None)
if has_optional:
assert m.nodefer_none_optional(None)
def test_capsule_with_destructor(capture):
import pybind11_tests as m
pytest.gc_collect()
with capture:
a = m.return_capsule_with_destructor()
del a