From 57a36633c43831ead3bf25c1fa841ba16a9d239e Mon Sep 17 00:00:00 2001 From: Cris Luengo Date: Fri, 25 Jun 2021 18:56:17 -0600 Subject: [PATCH] fix: enable py::implicitly_convertible for py::class_-wrapped types (#3059) * Allow casting from None to a custom object, closes #2778 * ci.yml patch from the smart_holder branch for full CI coverage. --- include/pybind11/detail/type_caster_base.h | 20 +++++++++++++------- tests/test_methods_and_attributes.cpp | 18 ++++++++++++++++++ tests/test_methods_and_attributes.py | 11 +++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 0dd9d48e55..a8d3938c7c 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -657,12 +657,6 @@ class type_caster_generic { PYBIND11_NOINLINE bool load_impl(handle src, bool convert) { if (!src) return false; if (!typeinfo) return try_load_foreign_module_local(src); - 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; - } auto &this_ = static_cast(*this); this_.check_holder_compat(); @@ -731,7 +725,19 @@ class type_caster_generic { } // Global typeinfo has precedence over foreign module_local - return try_load_foreign_module_local(src); + if (try_load_foreign_module_local(src)) { + return true; + } + + // Custom converters didn't take None, now we convert None to nullptr. + 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; + } + + return false; } diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index 7ddc9f3ce1..3ca2da880c 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -118,6 +118,14 @@ int none3(const std::shared_ptr &obj) { return obj ? obj->answer : - int none4(std::shared_ptr *obj) { return obj && *obj ? (*obj)->answer : -1; } int none5(const std::shared_ptr &obj) { return obj ? obj->answer : -1; } +// Issue #2778: implicit casting from None to object (not pointer) +class NoneCastTester { +public: + int answer = -1; + NoneCastTester() = default; + NoneCastTester(int v) : answer(v) {}; +}; + struct StrIssue { int val = -1; @@ -358,6 +366,16 @@ TEST_SUBMODULE(methods_and_attributes, m) { m.def("no_none_kwarg", &none2, "a"_a.none(false)); m.def("no_none_kwarg_kw_only", &none2, py::kw_only(), "a"_a.none(false)); + // test_casts_none + // Issue #2778: implicit casting from None to object (not pointer) + py::class_(m, "NoneCastTester") + .def(py::init<>()) + .def(py::init()) + .def(py::init([](py::none const&) { return NoneCastTester{}; })); + py::implicitly_convertible(); + m.def("ok_obj_or_none", [](NoneCastTester const& foo) { return foo.answer; }); + + // test_str_issue // Issue #283: __str__ called on uninitialized instance when constructor arguments invalid py::class_(m, "StrIssue") diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 8328dcd4f8..9c68de0a42 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -433,6 +433,17 @@ def test_accepts_none(msg): assert m.ok_none4(None) == -1 +def test_casts_none(msg): + """#2778: implicit casting from None to object (not pointer)""" + a = m.NoneCastTester() + assert m.ok_obj_or_none(a) == -1 + a = m.NoneCastTester(4) + assert m.ok_obj_or_none(a) == 4 + a = m.NoneCastTester(None) + assert m.ok_obj_or_none(a) == -1 + assert m.ok_obj_or_none(None) == -1 + + def test_str_issue(msg): """#283: __str__ called on uninitialized instance when constructor arguments invalid"""