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).
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index 1514fab..91b3e37 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -218,6 +218,8 @@
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 @@
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;
}
diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h
index ab9e1c3..ae168c4 100644
--- a/include/pybind11/functional.h
+++ b/include/pybind11/functional.h
@@ -22,9 +22,12 @@
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;
diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp
index a82145b..f2793c0 100644
--- a/tests/test_python_types.cpp
+++ b/tests/test_python_types.cpp
@@ -500,6 +500,18 @@
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");
diff --git a/tests/test_python_types.py b/tests/test_python_types.py
index 3e337d4..81a3ede 100644
--- a/tests/test_python_types.py
+++ b/tests/test_python_types.py
@@ -550,8 +550,22 @@
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