diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 7d485f63..c0f38fd7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1943,6 +1943,18 @@ struct arg : detail::arg_base { template arg_v operator=(T &&value) const; + /// Same as `arg_base::noconvert()`, but returns *this as arg&, not arg_base& + arg &noconvert(bool flag = true) { + arg_base::noconvert(flag); + return *this; + } + + /// Same as `arg_base::noconvert()`, but returns *this as arg&, not arg_base& + arg &none(bool flag = true) { + arg_base::none(flag); + return *this; + } + arg &policies(const from_python_policies &policies) { m_policies = policies; return *this; @@ -2013,6 +2025,12 @@ struct arg_v : arg { return *this; } + /// Same as `arg::policies()`, but returns *this as arg_v&, not arg& + arg_v &policies(const from_python_policies &policies) { + arg::policies(policies); + return *this; + } + /// The default value object value; bool value_is_nullptr = false; diff --git a/tests/test_return_value_policy_pack.cpp b/tests/test_return_value_policy_pack.cpp index 2e021b6d..84da0dce 100644 --- a/tests/test_return_value_policy_pack.cpp +++ b/tests/test_return_value_policy_pack.cpp @@ -436,4 +436,50 @@ TEST_SUBMODULE(return_value_policy_pack, m) { py::class_(m, "IntOwner").def_readonly("val", &IntOwner::val); m.def("call_callback_pass_int_owner_const_ptr", call_callback_pass_int_owner_const_ptr); + + // Ensure chaining py::arg() member functions works. + m.def( + "arg_chaining_noconvert_policies", + [](const std::function &cb) { + return cb("\x80" + "ArgNoconvertPolicies"); + }, + py::arg("cb").noconvert().policies(py::return_value_policy_pack(rvpb)), + rvpb); + m.def( + "arg_chaining_none_policies", + [](const std::function &cb) { + return cb("\x80" + "ArgNonePolicies"); + }, + py::arg("cb").none().policies(py::return_value_policy_pack(rvpb)), + rvpb); + m.def( + "arg_chaining_policies_noconvert", + [](const std::function &cb) { + return cb("\x80" + "ArgPoliciesNoconvert"); + }, + py::arg("cb").policies(py::return_value_policy_pack(rvpb)).noconvert(), + rvpb); + + // Ensure chaining py::arg_v() member functions works. + // This does not look very useful, but .policies() chaining was added because + // chaining for .noconvert() and .none() existed already. + m.def( + "arg_v_chaining_noconvert", + [](int num) { return num + 10; }, + (py::arg("num") = 2).noconvert()); + m.def("arg_v_chaining_none", [](int num) { return num + 20; }, (py::arg("num") = 3).none()); + m.def( + "arg_v_chaining_none_policies", + [](const std::function &cb) { + if (cb) { + return cb("\x80" + "ArgvNonePolicies"); + } + return std::string(""); + }, + (py::arg("cb") = py::none()).policies(py::return_value_policy_pack(rvpb)), + rvpb); } diff --git a/tests/test_return_value_policy_pack.py b/tests/test_return_value_policy_pack.py index 022ae644..8b45dc9b 100644 --- a/tests/test_return_value_policy_pack.py +++ b/tests/test_return_value_policy_pack.py @@ -337,3 +337,33 @@ def cb(int_owner): return int_owner.val + 40 assert m.call_callback_pass_int_owner_const_ptr(cb) == 543 + + +@pytest.mark.parametrize( + ("fn", "expected"), + [ + (m.arg_chaining_noconvert_policies, b"\x80ArgNoconvertPolicies"), + (m.arg_chaining_none_policies, b"\x80ArgNonePolicies"), + (m.arg_chaining_policies_noconvert, b"\x80ArgPoliciesNoconvert"), + ], +) +def test_arg_chaining(fn, expected): + assert fn(lambda arg: arg + b"Extra") == expected + b"Extra" + + +def test_arg_v_chaining_noconvert(): + assert m.arg_v_chaining_noconvert() == 12 + assert m.arg_v_chaining_noconvert(4) == 14 + + +def test_arg_v_chaining_none(): + assert m.arg_v_chaining_none() == 23 + assert m.arg_v_chaining_none(5) == 25 + + +def test_arg_v_chaining_none_policies(): + assert m.arg_v_chaining_none_policies() == b"" + assert ( + m.arg_v_chaining_none_policies(lambda arg: arg + b"Extra") + == b"\x80ArgvNonePoliciesExtra" + )