diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index b5e3b7b22c..6ee05cec41 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -176,11 +176,13 @@ struct argument_record { const char *name; ///< Argument name 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 + from_python_policies policies; - argument_record(const char *name, const char *descr, handle value, bool convert, bool none) - : name(name), descr(descr), value(value), convert(convert), none(none) {} + argument_record(const char *name, + const char *descr, + handle value, + const from_python_policies &policies) + : name(name), descr(descr), value(value), policies(policies) {} }; /// Internal data structure which holds metadata about a bound function (signature, overloads, @@ -212,8 +214,8 @@ struct function_record { /// Pointer to custom destructor for 'data' (if needed) void (*free_data)(function_record *ptr) = nullptr; - /// Return value policy associated with this function - return_value_policy policy = return_value_policy::automatic; + /// Return value policies associated with this function + return_value_policy_pack rvpp; /// True if name == '__init__' bool is_constructor : 1; @@ -359,7 +361,7 @@ struct type_record { inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) { args.reserve(f.nargs); - args_convert.reserve(f.nargs); + args_policies.reserve(f.nargs); } /// Tag for a new-style `__init__` defined in `detail/init.h` @@ -407,7 +409,12 @@ struct process_attribute : process_attribute {}; /// Process an attribute indicating the function's return value policy template <> struct process_attribute : process_attribute_default { - static void init(const return_value_policy &p, function_record *r) { r->policy = p; } + static void init(const return_value_policy &p, function_record *r) { r->rvpp.policy = p; } +}; +template <> +struct process_attribute + : process_attribute_default { + static void init(const return_value_policy_pack &rvpp, function_record *r) { r->rvpp = rvpp; } }; /// Process an attribute which indicates that this is an overloaded function associated with a @@ -455,7 +462,8 @@ inline void check_kw_only_arg(const arg &a, function_record *r) { inline void append_self_arg_if_needed(function_record *r) { if (r->is_method && r->args.empty()) { - r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false); + r->args.emplace_back( + "self", nullptr, handle(), from_python_policies(/*convert=*/true, /*none=*/false)); } } @@ -464,19 +472,27 @@ template <> struct process_attribute : process_attribute_default { static void init(const arg &a, function_record *r) { append_self_arg_if_needed(r); - r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); + r->args.emplace_back( + a.name, + nullptr, + handle(), + from_python_policies(a.m_policies.rvpp, !a.flag_noconvert, a.flag_none)); check_kw_only_arg(a, r); } }; +template <> +struct process_attribute : process_attribute {}; /// Process a keyword argument attribute (*with* a default value) template <> struct process_attribute : process_attribute_default { static void init(const arg_v &a, function_record *r) { if (r->is_method && r->args.empty()) { - r->args.emplace_back( - "self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false); + r->args.emplace_back("self", + /*descr=*/nullptr, + /*parent=*/handle(), + from_python_policies(/*convert=*/true, /*none=*/false)); } if (!a.value) { @@ -505,7 +521,11 @@ struct process_attribute : process_attribute_default { "more information."); #endif } - r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); + r->args.emplace_back( + a.name, + a.descr, + a.value.inc_ref(), + from_python_policies(a.m_policies.rvpp, !a.flag_noconvert, a.flag_none)); check_kw_only_arg(a, r); } @@ -667,7 +687,7 @@ using extract_guard_t = typename exactly_one_t, Extr /// Check the number of named arguments at compile time template ::value...), + size_t named = constexpr_sum(std::is_base_of::value...), size_t self = constexpr_sum(std::is_same::value...)> constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs); diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index e8139da20e..ce68810803 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -112,7 +112,7 @@ class type_caster> { explicit operator std::reference_wrapper() { return cast_op(subcaster); } }; -#define PYBIND11_TYPE_CASTER(type, py_name) \ +#define PYBIND11_TYPE_CASTER_IMPL(type, py_name, rvp_or_rvpp_type) \ protected: \ type value; \ \ @@ -124,15 +124,15 @@ public: int> \ = 0> \ static ::pybind11::handle cast( \ - T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ + T_ *src, const rvp_or_rvpp_type &rvp_or_rvpp, ::pybind11::handle parent) { \ if (!src) \ return ::pybind11::none().release(); \ - if (policy == ::pybind11::return_value_policy::take_ownership) { \ - auto h = cast(std::move(*src), policy, parent); \ + if (rvp_or_rvpp == ::pybind11::return_value_policy::take_ownership) { \ + auto h = cast(std::move(*src), rvp_or_rvpp, parent); \ delete src; \ return h; \ } \ - return cast(*src, policy, parent); \ + return cast(*src, rvp_or_rvpp, parent); \ } \ operator type *() { return &value; } /* NOLINT(bugprone-macro-parentheses) */ \ operator type &() { return value; } /* NOLINT(bugprone-macro-parentheses) */ \ @@ -140,6 +140,12 @@ public: template \ using cast_op_type = ::pybind11::detail::movable_cast_op_type +#define PYBIND11_TYPE_CASTER(type, py_name) \ + PYBIND11_TYPE_CASTER_IMPL(type, py_name, ::pybind11::return_value_policy) + +#define PYBIND11_TYPE_CASTER_RVPP(type, py_name) \ + PYBIND11_TYPE_CASTER_IMPL(type, py_name, ::pybind11::return_value_policy_pack) + template using is_std_char_type = any_of, /* std::string */ #if defined(PYBIND11_HAS_U8STRING) @@ -464,11 +470,12 @@ struct string_caster { return true; } - static handle cast(const StringType &src, return_value_policy policy, handle /* parent */) { + static handle + cast(const StringType &src, const return_value_policy_pack &rvpp, handle /* parent */) { const char *buffer = reinterpret_cast(src.data()); auto nbytes = ssize_t(src.size() * sizeof(CharT)); handle s; - if (policy == return_value_policy::_return_as_bytes) { + if (rvpp.policy == return_value_policy::_return_as_bytes) { s = PyBytes_FromStringAndSize(buffer, nbytes); } else { s = decode_utfN(buffer, nbytes); @@ -676,22 +683,22 @@ class tuple_caster { } template - static handle cast(T &&src, return_value_policy policy, handle parent) { - return cast_impl(std::forward(src), policy, parent, indices{}); + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { + return cast_impl(std::forward(src), rvpp, parent, indices{}); } // copied from the PYBIND11_TYPE_CASTER macro template - static handle cast(T *src, return_value_policy policy, handle parent) { + static handle cast(T *src, const return_value_policy_pack &rvpp, handle parent) { if (!src) { return none().release(); } - if (policy == return_value_policy::take_ownership) { - auto h = cast(std::move(*src), policy, parent); + if (rvpp.policy == return_value_policy::take_ownership) { + auto h = cast(std::move(*src), rvpp, parent); delete src; return h; } - return cast(*src, policy, parent); + return cast(*src, rvpp, parent); } static constexpr auto name @@ -733,12 +740,14 @@ class tuple_caster { /* Implementation: Convert a C++ tuple into a Python tuple */ template - static handle - cast_impl(T &&src, return_value_policy policy, handle parent, index_sequence) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(src, policy, parent); - PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(policy, parent); + static handle cast_impl(T &&src, + const return_value_policy_pack &rvpp, + handle parent, + index_sequence) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(src, rvpp, parent); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(rvpp, parent); std::array entries{{reinterpret_steal( - make_caster::cast(std::get(std::forward(src)), policy, parent))...}}; + make_caster::cast(std::get(std::forward(src)), rvpp.get(Is), parent))...}}; for (const auto &entry : entries) { if (!entry) { return handle(); @@ -1259,12 +1268,16 @@ tuple make_tuple() { return tuple(0); } -template -tuple make_tuple(Args &&...args_) { +PYBIND11_NAMESPACE_BEGIN(detail) + +template +tuple make_tuple_rvpp_impl(const return_value_policy_pack &rvpp, + detail::index_sequence, + Args &&...args_) { constexpr size_t size = sizeof...(Args); std::array args{{reinterpret_steal( - detail::make_caster::cast(std::forward(args_), policy, nullptr))...}}; - for (size_t i = 0; i < args.size(); i++) { + detail::make_caster::cast(std::forward(args_), rvpp.get(Is), nullptr))...}}; + for (size_t i = 0; i != args.size(); i++) { if (!args[i]) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) throw cast_error_unable_to_convert_call_arg(std::to_string(i)); @@ -1282,26 +1295,40 @@ tuple make_tuple(Args &&...args_) { return result; } +PYBIND11_NAMESPACE_END(detail) + +template +tuple make_tuple_rvpp(const return_value_policy_pack &rvpp, Args &&...args_) { + using indices = detail::make_index_sequence; + return detail::make_tuple_rvpp_impl(rvpp, indices{}, std::forward(args_)...); +} + +template +tuple make_tuple(Args &&...args_) { + return make_tuple_rvpp(policy, std::forward(args_)...); +} + +struct arg; + +PYBIND11_NAMESPACE_BEGIN(detail) + /// \ingroup annotations /// Annotation for arguments -struct arg { +struct arg_base { /// 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) + constexpr explicit arg_base(const char *name = nullptr) : name(name), flag_noconvert(false), flag_none(true) {} + /// Assign a value to this argument template 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; - } + arg_base &noconvert(bool flag = true); + /// 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; - } + arg_base &none(bool flag = true); 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 @@ -1309,10 +1336,35 @@ struct arg { bool flag_none : 1; ///< If set (the default), allow None to be passed to this argument }; +PYBIND11_NAMESPACE_END(detail) + +struct arg : detail::arg_base { + // NOLINTNEXTLINE(google-explicit-constructor) + arg(const detail::arg_base &arg_b) : detail::arg_base{arg_b} {} + + explicit arg(const char *name = nullptr) : detail::arg_base{name} {} + + /// Assign a value to this argument + template + arg_v operator=(T &&value) const; + + arg &policies(const from_python_policies &policies) { + m_policies = policies; + return *this; + } + + from_python_policies m_policies; +}; + /// \ingroup annotations /// Annotation for arguments with values struct arg_v : arg { +#if !defined(_MSC_VER) + // error C2248: 'pybind11::arg_v::arg_v': + // cannot access private member declared in class 'pybind11::arg_v' private: +#endif + friend struct arg_base; template arg_v(arg &&base, T &&x, const char *descr = nullptr) : arg(base), value(reinterpret_steal(detail::make_caster::cast( @@ -1374,6 +1426,25 @@ struct kw_only {}; /// an unnamed '/' argument (in Python 3.8) struct pos_only {}; +PYBIND11_NAMESPACE_BEGIN(detail) + +template +arg_v arg_base::operator=(T &&value) const { + return {*this, std::forward(value)}; +} + +inline arg_base &arg_base::noconvert(bool flag) { + flag_noconvert = flag; + return *this; +} + +inline arg_base &arg_base::none(bool flag) { + flag_none = flag; + return *this; +} + +PYBIND11_NAMESPACE_END(detail) + template arg_v arg::operator=(T &&value) const { return {*this, std::forward(value)}; @@ -1387,7 +1458,9 @@ inline namespace literals { /** \rst String literal version of `arg` \endrst */ -constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } +constexpr detail::arg_base operator"" _a(const char *name, size_t) { + return detail::arg_base(name); +} } // namespace literals PYBIND11_NAMESPACE_BEGIN(detail) @@ -1411,7 +1484,7 @@ struct function_call { std::vector args; /// The `convert` value the arguments should be loaded with - std::vector args_convert; + std::vector args_policies; /// Extra references for the optional `py::args` and/or `py::kwargs` arguments (which, if /// present, are also in `args` but without a reference). @@ -1472,11 +1545,11 @@ class argument_loader { template bool load_impl_sequence(function_call &call, index_sequence) { #ifdef __cpp_fold_expressions - if ((... || !std::get(argcasters).load(call.args[Is], call.args_convert[Is]))) { + if ((... || !std::get(argcasters).load(call.args[Is], call.args_policies[Is]))) { return false; } #else - for (bool r : {std::get(argcasters).load(call.args[Is], call.args_convert[Is])...}) { + for (bool r : {std::get(argcasters).load(call.args[Is], call.args_policies[Is])...}) { if (!r) { return false; } @@ -1493,14 +1566,11 @@ class argument_loader { std::tuple...> argcasters; }; -/// Helper class which collects only positional arguments for a Python function call. -/// A fancier version below can collect any argument, but this one is optimal for simple calls. -template -class simple_collector { +class simple_collector_rvpp { public: template - explicit simple_collector(Ts &&...values) - : m_args(pybind11::make_tuple(std::forward(values)...)) {} + explicit simple_collector_rvpp(const return_value_policy_pack &rvpp, Ts &&...values) + : m_args(pybind11::make_tuple_rvpp(rvpp, std::forward(values)...)) {} const tuple &args() const & { return m_args; } dict kwargs() const { return {}; } @@ -1520,17 +1590,25 @@ class simple_collector { tuple m_args; }; -/// Helper class which collects positional, keyword, * and ** arguments for a Python function call +/// Helper class which collects only positional arguments for a Python function call. +/// A fancier version below can collect any argument, but this one is optimal for simple calls. template -class unpacking_collector { +class simple_collector : public simple_collector_rvpp { public: template - explicit unpacking_collector(Ts &&...values) { + explicit simple_collector(Ts &&...values) + : simple_collector_rvpp(policy, std::forward(values)...) {} +}; + +class unpacking_collector_rvpp { +public: + template + explicit unpacking_collector_rvpp(const return_value_policy_pack &rvpp, Ts &&...values) { // Tuples aren't (easily) resizable so a list is needed for collection, // but the actual function call strictly requires a tuple. auto args_list = list(); - using expander = int[]; - (void) expander{0, (process(args_list, std::forward(values)), 0)...}; + using indices = detail::make_index_sequence; + ctor_helper(args_list, rvpp, indices{}, std::forward(values)...); m_args = std::move(args_list); } @@ -1551,10 +1629,19 @@ class unpacking_collector { } private: + template + void ctor_helper(list args_list, + const return_value_policy_pack &rvpp, + detail::index_sequence, + Ts &&...values) { + using expander = int[]; + (void) expander{0, (process(args_list, rvpp.get(Is), std::forward(values)), 0)...}; + } + template - void process(list &args_list, T &&x) { + void process(list &args_list, const return_value_policy_pack &rvpp, T &&x) { auto o = reinterpret_steal( - detail::make_caster::cast(std::forward(x), policy, {})); + detail::make_caster::cast(std::forward(x), rvpp, {})); if (!o) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size())); @@ -1566,13 +1653,14 @@ class unpacking_collector { args_list.append(std::move(o)); } - void process(list &args_list, detail::args_proxy ap) { + void + process(list &args_list, const return_value_policy_pack & /*rvpp*/, detail::args_proxy ap) { for (auto a : ap) { args_list.append(a); } } - void process(list & /*args_list*/, arg_v a) { + void process(list & /*args_list*/, const return_value_policy_pack & /*rvpp*/, arg_v a) { if (!a.name) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) nameless_argument_error(); @@ -1597,7 +1685,9 @@ class unpacking_collector { m_kwargs[a.name] = std::move(a.value); } - void process(list & /*args_list*/, detail::kwargs_proxy kp) { + void process(list & /*args_list*/, + const return_value_policy_pack & /*rvpp*/, + detail::kwargs_proxy kp) { if (!kp) { return; } @@ -1639,6 +1729,15 @@ class unpacking_collector { dict m_kwargs; }; +/// Helper class which collects positional, keyword, * and ** arguments for a Python function call +template +class unpacking_collector : public unpacking_collector_rvpp { +public: + template + explicit unpacking_collector(Ts &&...values) + : unpacking_collector_rvpp(policy, std::forward(values)...) {} +}; + // [workaround(intel)] Separate function required here // We need to put this into a separate function because the Intel compiler // fails to compile enable_if_t...>::value> @@ -1648,6 +1747,12 @@ constexpr bool args_are_all_positional() { return all_of...>::value; } +template ()>> +simple_collector_rvpp collect_arguments_rvpp(const return_value_policy_pack &rvpp, + Args &&...args) { + return simple_collector_rvpp(rvpp, std::forward(args)...); +} + /// Collect only positional arguments for a Python function call template collect_arguments(Args &&...args) { return simple_collector(std::forward(args)...); } -/// Collect all arguments, including keywords and unpacking (only instantiated when needed) -template ()>> -unpacking_collector collect_arguments(Args &&...args) { +template ()>> +unpacking_collector_rvpp collect_arguments_rvpp(const return_value_policy_pack &rvpp, + Args &&...args) { // Following argument order rules for generalized unpacking according to PEP 448 static_assert(constexpr_last() < constexpr_first() @@ -1668,24 +1771,39 @@ unpacking_collector collect_arguments(Args &&...args) { < constexpr_first(), "Invalid function call: positional args must precede keywords and ** unpacking; " "* unpacking must precede ** unpacking"); + return unpacking_collector_rvpp(rvpp, std::forward(args)...); +} + +/// Collect all arguments, including keywords and unpacking (only instantiated when needed) +template ()>> +unpacking_collector collect_arguments(Args &&...args) { return unpacking_collector(std::forward(args)...); } template -template -object object_api::operator()(Args &&...args) const { +template +object object_api::call_with_policies(const return_value_policy_pack &rvpp, + Args &&...args) const { #ifndef NDEBUG if (!PyGILState_Check()) { - pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure."); + pybind11_fail("pybind11::object_api<>::call_with_policies() PyGILState_Check() failure."); } #endif - return detail::collect_arguments(std::forward(args)...).call(derived().ptr()); + return detail::collect_arguments_rvpp(rvpp, std::forward(args)...).call(derived().ptr()); +} + +template +template +object object_api::operator()(Args &&...args) const { + return call_with_policies(policy, std::forward(args)...); } template template object object_api::call(Args &&...args) const { - return operator()(std::forward(args)...); + return call_with_policies(policy, std::forward(args)...); } PYBIND11_NAMESPACE_END(detail) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index ac06a76e31..0a8ddaeb65 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -560,6 +560,72 @@ enum class return_value_policy : uint8_t { #define PYBIND11_HAS_RETURN_VALUE_POLICY_RETURN_AS_BYTES #define PYBIND11_HAS_RETURN_VALUE_POLICY_CLIF_AUTOMATIC +struct return_value_policy_pack { + using vec_rvpp_t = std::vector; + vec_rvpp_t vec_rvpp; + return_value_policy policy = return_value_policy::automatic; + + return_value_policy_pack() = default; + + // NOLINTNEXTLINE(google-explicit-constructor) + return_value_policy_pack(return_value_policy policy) : policy(policy) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + return_value_policy_pack(std::initializer_list vec_rvpp) + : vec_rvpp(vec_rvpp) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + operator return_value_policy() const { return policy; } + + return_value_policy_pack(const vec_rvpp_t &vec_rvpp, return_value_policy policy) + : vec_rvpp(vec_rvpp), policy(policy) {} + + return_value_policy_pack override_policy(return_value_policy new_policy) const { + return return_value_policy_pack(vec_rvpp, new_policy); + } + + return_value_policy_pack + override_policy(return_value_policy (*func)(return_value_policy)) const { + return override_policy(func(policy)); + } + + return_value_policy_pack get(std::size_t i) const { + if (vec_rvpp.empty()) { + return policy; + } + return vec_rvpp.at(i); + } +}; + +struct from_python_policies { + return_value_policy_pack rvpp; + bool convert : 1; ///< True if the argument is allowed to convert when loading + bool none : 1; ///< True if None is allowed when loading + + from_python_policies() + : rvpp(return_value_policy::automatic_reference), convert(true), none(true) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + from_python_policies(bool convert, bool none = true) : convert(convert), none(none) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + from_python_policies(const return_value_policy_pack &rvpp) + : rvpp(rvpp), convert(true), none(true) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + operator bool() const { return convert; } + + from_python_policies(const return_value_policy_pack &rvpp, bool convert, bool none) + : rvpp(rvpp), convert(convert), none(none) {} + + from_python_policies get(std::size_t i) const { + if (rvpp.vec_rvpp.empty()) { + return from_python_policies(convert, none); + } + return from_python_policies(rvpp.vec_rvpp.at(i)); + } +}; + PYBIND11_NAMESPACE_BEGIN(detail) inline static constexpr int log2(size_t n, int k = 0) { diff --git a/include/pybind11/detail/type_caster_odr_guard.h b/include/pybind11/detail/type_caster_odr_guard.h index 71d074d1d7..46ea1af8d5 100644 --- a/include/pybind11/detail/type_caster_odr_guard.h +++ b/include/pybind11/detail/type_caster_odr_guard.h @@ -117,11 +117,12 @@ struct type_caster_odr_guard : TypeCasterType { // The original author of this function is @amauryfa template - static handle cast(CType &&src, return_value_policy policy, handle parent, Arg &&...arg) { + static handle + cast(CType &&src, const return_value_policy_pack &rvpp, handle parent, Arg &&...arg) { if (translation_unit_local) { } return TypeCasterType::cast( - std::forward(src), policy, parent, std::forward(arg)...); + std::forward(src), rvpp, parent, std::forward(arg)...); } }; diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index 87ec4d10cb..9c6c60649f 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -23,10 +23,10 @@ struct type_caster> { using function_type = Return (*)(Args...); public: - bool load(handle src, bool convert) { + bool load(handle src, from_python_policies fpp) { if (src.is_none()) { // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) { + if (!fpp.convert) { return false; } return true; @@ -102,29 +102,32 @@ struct type_caster> { // to emulate 'move initialization capture' in C++11 struct func_wrapper { func_handle hfunc; - explicit func_wrapper(func_handle &&hf) noexcept : hfunc(std::move(hf)) {} + return_value_policy_pack rvpp; + func_wrapper(func_handle &&hf, const return_value_policy_pack &rvpp) noexcept + : hfunc(std::move(hf)), rvpp(rvpp) {} Return operator()(Args... args) const { gil_scoped_acquire acq; // casts the returned object as a rvalue to the return type - return hfunc.f(std::forward(args)...).template cast(); + return hfunc.f.call_with_policies(rvpp, std::forward(args)...) + .template cast(); } }; - value = func_wrapper(func_handle(std::move(func))); + value = func_wrapper(func_handle(std::move(func)), fpp.rvpp); return true; } template - static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { + static handle cast(Func &&f_, const return_value_policy_pack &rvpp, handle /* parent */) { if (!f_) { return none().release(); } auto result = f_.template target(); if (result) { - return cpp_function(*result, policy).release(); + return cpp_function(*result, rvpp).release(); } - return cpp_function(std::forward(f_), policy).release(); + return cpp_function(std::forward(f_), rvpp).release(); } PYBIND11_TYPE_CASTER(type, diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index eef03cf74f..f849d7f35d 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -238,17 +238,15 @@ class cpp_function : public function { auto *cap = const_cast(reinterpret_cast(data)); /* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */ - return_value_policy policy - = return_value_policy_override::policy(call.func.policy); + return_value_policy_pack rvpp + = call.func.rvpp.override_policy(return_value_policy_override::policy); /* Function scope guard -- defaults to the compile-to-nothing `void_type` */ using Guard = extract_guard_t; /* Perform the function call */ - handle result - = cast_out::cast(std::move(args_converter).template call(cap->f), - policy, - call.parent); + handle result = cast_out::cast( + std::move(args_converter).template call(cap->f), rvpp, call.parent); /* Invoke call policy post-call hook */ process_attributes::postcall(call, result); @@ -771,7 +769,7 @@ class cpp_function : public function { call.init_self = PyTuple_GET_ITEM(args_in, 0); call.args.emplace_back(reinterpret_cast(&self_value_and_holder)); - call.args_convert.push_back(false); + call.args_policies.emplace_back(false); ++args_copied; } @@ -787,12 +785,15 @@ class cpp_function : public function { } handle arg(PyTuple_GET_ITEM(args_in, args_copied)); - if (arg_rec && !arg_rec->none && arg.is_none()) { + if (arg_rec && !arg_rec->policies.none && arg.is_none()) { bad_arg = true; break; } call.args.push_back(arg); - call.args_convert.push_back(arg_rec ? arg_rec->convert : true); + call.args_policies.emplace_back( + arg_rec ? from_python_policies( + arg_rec->policies.rvpp, arg_rec->policies.convert, false) + : from_python_policies(true, false)); } if (bad_arg) { continue; // Maybe it was meant for another overload (issue #688) @@ -816,7 +817,7 @@ class cpp_function : public function { } if (value) { call.args.push_back(value); - call.args_convert.push_back(arg_rec.convert); + call.args_policies.push_back(arg_rec.policies); } else { break; } @@ -852,7 +853,7 @@ class cpp_function : public function { value = arg_rec.value; } - if (!arg_rec.none && value.is_none()) { + if (!arg_rec.policies.none && value.is_none()) { break; } @@ -864,7 +865,7 @@ class cpp_function : public function { } call.args.push_back(value); - call.args_convert.push_back(arg_rec.convert); + call.args_policies.push_back(arg_rec.policies); } else { break; } @@ -902,7 +903,7 @@ class cpp_function : public function { } else { call.args[func.nargs_pos] = extra_args; } - call.args_convert.push_back(false); + call.args_policies.emplace_back(false); call.args_ref = std::move(extra_args); } @@ -912,26 +913,27 @@ class cpp_function : public function { kwargs = dict(); // If we didn't get one, send an empty one } call.args.push_back(kwargs); - call.args_convert.push_back(false); + call.args_policies.emplace_back(false); call.kwargs_ref = std::move(kwargs); } // 5. Put everything in a vector. Not technically step 5, we've been building it // in `call.args` all along. #if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) { + if (call.args.size() != func.nargs || call.args_policies.size() != func.nargs) { pybind11_fail("Internal error: function call dispatcher inserted wrong number " "of arguments!"); } #endif - std::vector second_pass_convert; + std::vector second_pass_args_policies; if (overloaded) { // We're in the first no-convert pass, so swap out the conversion flags for a // set of all-false flags. If the call fails, we'll swap the flags back in for // the conversion-allowed call below. - second_pass_convert.resize(func.nargs, false); - call.args_convert.swap(second_pass_convert); + second_pass_args_policies.resize( + func.nargs, from_python_policies(false, false)); // m_policies + call.args_policies.swap(second_pass_args_policies); } // 6. Call the function. @@ -951,10 +953,10 @@ class cpp_function : public function { // permits conversion (i.e. it hasn't been explicitly specified `.noconvert()`) // then add this call to the list of second pass overloads to try. for (size_t i = func.is_method ? 1 : 0; i < pos_args; i++) { - if (second_pass_convert[i]) { + if (second_pass_args_policies[i].convert) { // Found one: swap the converting flags back in and store the call for // the second pass. - call.args_convert.swap(second_pass_convert); + call.args_policies.swap(second_pass_args_policies); second_pass.push_back(std::move(call)); break; } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f11ed5da78..0be372c750 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -45,6 +45,7 @@ struct arg; struct arg_v; PYBIND11_NAMESPACE_BEGIN(detail) +struct arg_base; class args_proxy; bool isinstance_generic(handle obj, const std::type_info &tp); @@ -124,6 +125,9 @@ class object_api : public pyobject_tag { template bool contains(T &&item) const; + template + object call_with_policies(const return_value_policy_pack &rvpp, Args &&...args) const; + /** \rst Assuming the Python object is a function or implements the ``__call__`` protocol, ``operator()`` invokes the underlying function, passing an @@ -1276,7 +1280,7 @@ class args_proxy : public handle { /// Python argument categories (using PEP 448 terms) template -using is_keyword = std::is_base_of; +using is_keyword = std::is_base_of; template using is_s_unpacking = std::is_same; // * unpacking template diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index ed70511965..d4e4b93aeb 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -19,6 +19,7 @@ #include #include #include +#include #include // See `detail/common.h` for implementation of these guards. @@ -85,14 +86,15 @@ struct set_caster { } template - static handle cast(T &&src, return_value_policy policy, handle parent) { + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { + return_value_policy_pack rvpp_local = rvpp; if (!std::is_lvalue_reference::value) { - policy = return_value_policy_override::policy(policy); + rvpp_local = rvpp.override_policy(return_value_policy_override::policy); } pybind11::set s; for (auto &&value : src) { auto value_ = reinterpret_steal( - key_conv::cast(detail::forward_like(value), policy, parent)); + key_conv::cast(detail::forward_like(value), rvpp_local, parent)); if (!value_ || !s.add(std::move(value_))) { return handle(); } @@ -135,19 +137,19 @@ struct map_caster { } template - static handle cast(T &&src, return_value_policy policy, handle parent) { + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { dict d; - return_value_policy policy_key = policy; - return_value_policy policy_value = policy; + return_value_policy_pack rvpp_key = rvpp.get(0); + return_value_policy_pack rvpp_value = rvpp.get(1); if (!std::is_lvalue_reference::value) { - policy_key = return_value_policy_override::policy(policy_key); - policy_value = return_value_policy_override::policy(policy_value); + rvpp_key = rvpp_key.override_policy(return_value_policy_override::policy); + rvpp_value = rvpp_value.override_policy(return_value_policy_override::policy); } for (auto &&kv : src) { auto key = reinterpret_steal( - key_conv::cast(detail::forward_like(kv.first), policy_key, parent)); + key_conv::cast(detail::forward_like(kv.first), rvpp_key, parent)); auto value = reinterpret_steal( - value_conv::cast(detail::forward_like(kv.second), policy_value, parent)); + value_conv::cast(detail::forward_like(kv.second), rvpp_value, parent)); if (!key || !value) { return handle(); } @@ -191,15 +193,16 @@ struct list_caster { public: template - static handle cast(T &&src, return_value_policy policy, handle parent) { + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { + return_value_policy_pack rvpp_local = rvpp; if (!std::is_lvalue_reference::value) { - policy = return_value_policy_override::policy(policy); + rvpp_local = rvpp.override_policy(return_value_policy_override::policy); } list l(src.size()); ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(detail::forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), rvpp_local, parent)); if (!value_) { return handle(); } @@ -208,7 +211,7 @@ struct list_caster { return l.release(); } - PYBIND11_TYPE_CASTER(Type, const_name("List[") + value_conv::name + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(Type, const_name("List[") + value_conv::name + const_name("]")); }; template @@ -258,12 +261,12 @@ struct array_caster { } template - static handle cast(T &&src, return_value_policy policy, handle parent) { + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { list l(src.size()); ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(detail::forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), rvpp, parent)); if (!value_) { return handle(); } @@ -272,12 +275,12 @@ struct array_caster { return l.release(); } - PYBIND11_TYPE_CASTER(ArrayType, - const_name("List[") + value_conv::name - + const_name(const_name(""), - const_name("[") + const_name() - + const_name("]")) - + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(ArrayType, + const_name("List[") + value_conv::name + + const_name(const_name(""), + const_name("[") + const_name() + + const_name("]")) + + const_name("]")); }; template @@ -309,15 +312,16 @@ struct optional_caster { using value_conv = make_caster; template - static handle cast(T &&src, return_value_policy policy, handle parent) { + static handle cast(T &&src, const return_value_policy_pack &rvpp, handle parent) { if (!src) { return none().release(); } + return_value_policy_pack rvpp_local = rvpp; if (!std::is_lvalue_reference::value) { - policy = return_value_policy_override::policy(policy); + rvpp_local = rvpp.override_policy(return_value_policy_override::policy); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - return value_conv::cast(*std::forward(src), policy, parent); + return value_conv::cast(*std::forward(src), rvpp_local, parent); } bool load(handle src, bool convert) { @@ -336,7 +340,7 @@ struct optional_caster { return true; } - PYBIND11_TYPE_CASTER(Type, const_name("Optional[") + value_conv::name + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(Type, const_name("Optional[") + value_conv::name + const_name("]")); }; #if defined(PYBIND11_HAS_OPTIONAL) @@ -359,14 +363,14 @@ struct type_caster /// Visit a variant and cast any found type to Python struct variant_caster_visitor { - return_value_policy policy; + return_value_policy_pack rvpp; handle parent; using result_type = handle; // required by boost::variant in C++11 template result_type operator()(T &&src) const { - return make_caster::cast(std::forward(src), policy, parent); + return make_caster::cast(std::forward(src), rvpp, parent); } }; @@ -417,15 +421,15 @@ struct variant_caster> { } template - static handle cast(Variant &&src, return_value_policy policy, handle parent) { - return visit_helper::call(variant_caster_visitor{policy, parent}, + static handle cast(Variant &&src, const return_value_policy_pack &rvpp, handle parent) { + return visit_helper::call(variant_caster_visitor{rvpp, parent}, std::forward(src)); } using Type = V; - PYBIND11_TYPE_CASTER(Type, - const_name("Union[") + detail::concat(make_caster::name...) - + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(Type, + const_name("Union[") + detail::concat(make_caster::name...) + + const_name("]")); }; #if defined(PYBIND11_HAS_VARIANT) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3a1ce8949d..a704d8e1e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -168,6 +168,7 @@ set(PYBIND11_TEST_FILES test_pickling test_pytypes test_return_value_policy_override + test_return_value_policy_pack test_sequences_and_iterators test_smart_ptr test_stl diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index c8e065bc0d..aefe52a878 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -378,7 +378,14 @@ TEST_SUBMODULE(factory_constructors, m) { } static void operator delete(void *p, size_t) { py::print("noisy delete"); +#if defined(__MINGW32__) + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_GCC("-Wmismatched-new-delete") +#endif ::operator delete(p); +#if defined(__MINGW32__) + PYBIND11_WARNING_POP +#endif } static void operator delete(void *, void *) { py::print("noisy placement delete"); } }; diff --git a/tests/test_gil_scoped.py b/tests/test_gil_scoped.py index fc8af9b77c..9e4b2c9ead 100644 --- a/tests/test_gil_scoped.py +++ b/tests/test_gil_scoped.py @@ -144,7 +144,9 @@ def _intentional_deadlock(): m.intentional_deadlock() -ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,) +ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS[ + :1 +] # + (_intentional_deadlock,) def _run_in_process(target, *args, **kwargs): diff --git a/tests/test_return_value_policy_pack.cpp b/tests/test_return_value_policy_pack.cpp new file mode 100644 index 0000000000..2e4899fa8b --- /dev/null +++ b/tests/test_return_value_policy_pack.cpp @@ -0,0 +1,248 @@ +#include +#include + +#include "pybind11_tests.h" + +#include +#include +#include +#include +#include +#include + +#if defined(PYBIND11_HAS_OPTIONAL) +# include +#endif + +#if defined(PYBIND11_HAS_VARIANT) +# include +#endif + +namespace { + +using PairString = std::pair; + +PairString return_pair_string() { return PairString({"", ""}); } + +using NestedPairString = std::pair; + +NestedPairString return_nested_pair_string() { + return NestedPairString(return_pair_string(), return_pair_string()); +} + +using MapString = std::map; + +MapString return_map_string() { return MapString({return_pair_string()}); } + +using MapPairString = std::map; + +MapPairString return_map_pair_string() { return MapPairString({return_nested_pair_string()}); } + +using SetPairString = std::set; + +SetPairString return_set_pair_string() { return SetPairString({return_pair_string()}); } + +using VectorPairString = std::vector; + +VectorPairString return_vector_pair_string() { return VectorPairString({return_pair_string()}); } + +using ArrayPairString = std::array; + +ArrayPairString return_array_pair_string() { return ArrayPairString({{return_pair_string()}}); } + +#if defined(PYBIND11_HAS_OPTIONAL) + +using OptionalPairString = std::optional; + +OptionalPairString return_optional_pair_string() { + return OptionalPairString(return_pair_string()); +} + +#endif // PYBIND11_HAS_OPTIONAL + +#if defined(PYBIND11_HAS_VARIANT) + +using VariantPairString = std::variant; + +VariantPairString return_variant_pair_string() { return VariantPairString(return_pair_string()); } + +#endif // PYBIND11_HAS_VARIANT + +std::string call_callback_pass_pair_string(const std::function &cb) { + auto p = return_pair_string(); + return cb(p); +} + +std::string level_0_si(int num) { return "level_0_si_" + std::to_string(num); } +int level_0_is(const std::string &s) { return 100 + std::atoi(s.c_str()); } + +using level_1_callback_si = std::function; +using level_2_callback_si = std::function; +using level_3_callback_si = std::function; +using level_4_callback_si = std::function; + +using level_1_callback_is = std::function; +using level_2_callback_is = std::function; +using level_3_callback_is = std::function; +using level_4_callback_is = std::function; + +std::string call_level_1_callback_si(const level_1_callback_si &cb) { return cb(1001); } +std::string call_level_2_callback_si(const level_2_callback_si &cb) { return cb(level_0_si); } +std::string call_level_3_callback_si(const level_3_callback_si &cb) { + return cb(call_level_1_callback_si); +} +std::string call_level_4_callback_si(const level_4_callback_si &cb) { + return cb(call_level_2_callback_si); +} + +int call_level_1_callback_is(const level_1_callback_is &cb) { return cb("101"); } +int call_level_2_callback_is(const level_2_callback_is &cb) { return cb(level_0_is); } +int call_level_3_callback_is(const level_3_callback_is &cb) { + return cb(call_level_1_callback_is); +} +int call_level_4_callback_is(const level_4_callback_is &cb) { + return cb(call_level_2_callback_is); +} + +} // namespace + +TEST_SUBMODULE(return_value_policy_pack, m) { + auto rvpc = py::return_value_policy::_clif_automatic; + auto rvpb = py::return_value_policy::_return_as_bytes; + + m.def("return_tuple_str_str", []() { return return_pair_string(); }); + m.def( + "return_tuple_bytes_bytes", []() { return return_pair_string(); }, rvpb); + m.def( + "return_tuple_str_bytes", + []() { return return_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_tuple_bytes_str", + []() { return return_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); + + m.def("return_nested_tuple_str", []() { return return_nested_pair_string(); }); + m.def( + "return_nested_tuple_bytes", []() { return return_nested_pair_string(); }, rvpb); + m.def( + "return_nested_tuple_str_bytes_bytes_str", + []() { return return_nested_pair_string(); }, + py::return_value_policy_pack({{rvpc, rvpb}, {rvpb, rvpc}})); + m.def( + "return_nested_tuple_bytes_str_str_bytes", + []() { return return_nested_pair_string(); }, + py::return_value_policy_pack({{rvpb, rvpc}, {rvpc, rvpb}})); + + m.def("return_dict_str_str", []() { return return_map_string(); }); + m.def( + "return_dict_bytes_bytes", []() { return return_map_string(); }, rvpb); + m.def( + "return_dict_str_bytes", + []() { return return_map_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_dict_bytes_str", + []() { return return_map_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); + + m.def( + "return_map_sbbs", + []() { return return_map_pair_string(); }, + py::return_value_policy_pack({{rvpc, rvpb}, {rvpb, rvpc}})); + m.def( + "return_map_bssb", + []() { return return_map_pair_string(); }, + py::return_value_policy_pack({{rvpb, rvpc}, {rvpc, rvpb}})); + + m.def( + "return_set_sb", + []() { return return_set_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_set_bs", + []() { return return_set_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); + + m.def( + "return_vector_sb", + []() { return return_vector_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_vector_bs", + []() { return return_vector_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); + + m.def( + "return_array_sb", + []() { return return_array_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_array_bs", + []() { return return_array_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); + + m.attr("PYBIND11_HAS_OPTIONAL") = +#if !defined(PYBIND11_HAS_OPTIONAL) + false; +#else + true; + m.def( + "return_optional_sb", + []() { return return_optional_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_optional_bs", + []() { return return_optional_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); +#endif + + m.attr("PYBIND11_HAS_VARIANT") = +#if !defined(PYBIND11_HAS_VARIANT) + false; +#else + true; + m.def( + "return_variant_sb", + []() { return return_variant_pair_string(); }, + py::return_value_policy_pack({rvpc, rvpb})); + m.def( + "return_variant_bs", + []() { return return_variant_pair_string(); }, + py::return_value_policy_pack({rvpb, rvpc})); +#endif + + auto arg_cb_b = py::arg("cb").policies(py::return_value_policy_pack(rvpb)); + + m.def("call_callback_pass_pair_default", call_callback_pass_pair_string, py::arg("cb")); + m.def("call_callback_pass_pair_s", + call_callback_pass_pair_string, + py::arg("cb").policies(py::return_value_policy_pack(rvpc))); + m.def("call_callback_pass_pair_b", call_callback_pass_pair_string, arg_cb_b); + m.def("call_callback_pass_pair_sb", + call_callback_pass_pair_string, + py::arg("cb").policies(py::return_value_policy_pack({{rvpc, rvpb}}))); + m.def("call_callback_pass_pair_bs", + call_callback_pass_pair_string, + py::arg("cb").policies(py::return_value_policy_pack({{rvpb, rvpc}}))); + + m.def("call_level_1_callback_si_s", call_level_1_callback_si); + m.def("call_level_2_callback_si_s", call_level_2_callback_si); + m.def("call_level_3_callback_si_s", call_level_3_callback_si); + m.def("call_level_4_callback_si_s", call_level_4_callback_si); + + m.def("call_level_1_callback_si_b", call_level_1_callback_si, arg_cb_b, rvpb); + m.def("call_level_2_callback_si_b", call_level_2_callback_si, arg_cb_b, rvpb); + m.def("call_level_3_callback_si_b", call_level_3_callback_si, arg_cb_b, rvpb); + m.def("call_level_4_callback_si_b", call_level_4_callback_si, arg_cb_b, rvpb); + + m.def("call_level_1_callback_is_s", call_level_1_callback_is); + m.def("call_level_2_callback_is_s", call_level_2_callback_is); + m.def("call_level_3_callback_is_s", call_level_3_callback_is); + m.def("call_level_4_callback_is_s", call_level_4_callback_is); + + m.def("call_level_1_callback_is_b", call_level_1_callback_is, arg_cb_b, rvpb); + m.def("call_level_2_callback_is_b", call_level_2_callback_is, rvpb); + m.def("call_level_3_callback_is_b", call_level_3_callback_is, rvpb); + m.def("call_level_4_callback_is_b", call_level_4_callback_is, rvpb); +} diff --git a/tests/test_return_value_policy_pack.py b/tests/test_return_value_policy_pack.py new file mode 100644 index 0000000000..23d50ef540 --- /dev/null +++ b/tests/test_return_value_policy_pack.py @@ -0,0 +1,249 @@ +import pytest + +from pybind11_tests import return_value_policy_pack as m + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_tuple_str_str, (str, str)), + (m.return_tuple_bytes_bytes, (bytes, bytes)), + (m.return_tuple_str_bytes, (str, bytes)), + (m.return_tuple_bytes_str, (bytes, str)), + ], +) +def test_return_pair_string(func, expected): + p = func() + assert isinstance(p, tuple) + assert len(p) == 2 + assert all(isinstance(e, t) for e, t in zip(p, expected)) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_nested_tuple_str, (str, str, str, str)), + (m.return_nested_tuple_bytes, (bytes, bytes, bytes, bytes)), + (m.return_nested_tuple_str_bytes_bytes_str, (str, bytes, bytes, str)), + (m.return_nested_tuple_bytes_str_str_bytes, (bytes, str, str, bytes)), + ], +) +def test_return_nested_pair_string(func, expected): + np = func() + assert isinstance(np, tuple) + assert len(np) == 2 + assert all(isinstance(e, t) for e, t in zip(sum(np, ()), expected)) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_dict_str_str, (str, str)), + (m.return_dict_bytes_bytes, (bytes, bytes)), + (m.return_dict_str_bytes, (str, bytes)), + (m.return_dict_bytes_str, (bytes, str)), + ], +) +def test_return_map_string(func, expected): + mp = func() + assert isinstance(mp, dict) + assert len(mp) == 1 + assert all(isinstance(e, t) for e, t in zip(tuple(mp.items())[0], expected)) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_map_sbbs, (str, bytes, bytes, str)), + (m.return_map_bssb, (bytes, str, str, bytes)), + ], +) +def test_return_dict_pair_string(func, expected): + mp = func() + assert isinstance(mp, dict) + assert len(mp) == 1 + assert all( + isinstance(e, t) for e, t in zip(sum(tuple(mp.items())[0], ()), expected) + ) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_set_sb, (str, bytes)), + (m.return_set_bs, (bytes, str)), + ], +) +def test_return_set_pair_string(func, expected): + sp = func() + assert isinstance(sp, set) + assert len(sp) == 1 + assert all(isinstance(e, t) for e, t in zip(sum(tuple(sp), ()), expected)) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.return_vector_sb, (str, bytes)), + (m.return_vector_bs, (bytes, str)), + (m.return_array_sb, (str, bytes)), + (m.return_array_bs, (bytes, str)), + ], +) +def test_return_list_pair_string(func, expected): + vp = func() + assert isinstance(vp, list) + assert len(vp) == 1 + assert all(isinstance(e, t) for e, t in zip(sum(vp, ()), expected)) + + +def _test_return_optional_variant_pair_string(fname, expected): + func = getattr(m, fname) + p = func() + assert isinstance(p, tuple) + assert len(p) == 2 + assert all(isinstance(e, t) for e, t in zip(p, expected)) + + +@pytest.mark.skipif(not m.PYBIND11_HAS_OPTIONAL, reason=" not available.") +@pytest.mark.parametrize( + ("fname", "expected"), + [ + ("return_optional_sb", (str, bytes)), + ("return_optional_bs", (bytes, str)), + ], +) +def test_return_optional_pair_string(fname, expected): + _test_return_optional_variant_pair_string(fname, expected) + + +@pytest.mark.skipif(not m.PYBIND11_HAS_VARIANT, reason=" not available.") +@pytest.mark.parametrize( + ("fname", "expected"), + [ + ("return_variant_sb", (str, bytes)), + ("return_variant_bs", (bytes, str)), + ], +) +def test_return_variant_pair_string(fname, expected): + _test_return_optional_variant_pair_string(fname, expected) + + +@pytest.mark.parametrize( + ("func", "expected"), + [ + (m.call_callback_pass_pair_default, repr(("", ""))), + (m.call_callback_pass_pair_s, repr(("", ""))), + (m.call_callback_pass_pair_b, repr((b"", b""))), + (m.call_callback_pass_pair_sb, repr(("", b""))), + (m.call_callback_pass_pair_bs, repr((b"", ""))), + ], +) +def test_call_callback_pass_pair_string(func, expected): + def cb(p): + assert isinstance(p, tuple) + assert len(p) == 2 + return repr(p) + + assert func(cb) == expected + + +def test_nested_callbacks_si_s(): + def cb_1(i): + assert isinstance(i, int) + return "cb_1_" + str(i) + + def cb_2(cb): + r = cb(20) + assert isinstance(r, str) + return "cb_2_" + r + + def cb_3(cb): + r = cb(cb_1) + assert isinstance(r, str) + return "cb_3_" + r + + def cb_4(cb): + r = cb(cb_2) + assert isinstance(r, str) + return "cb_4_" + r + + assert m.call_level_1_callback_si_s(cb_1) == "cb_1_1001" + assert m.call_level_2_callback_si_s(cb_2) == "cb_2_level_0_si_20" + assert m.call_level_3_callback_si_s(cb_3) == "cb_3_cb_1_1001" + assert m.call_level_4_callback_si_s(cb_4) == "cb_4_cb_2_level_0_si_20" + + +def test_nested_callbacks_si_b(): + def cb_1(i): + assert isinstance(i, int) + return b"\x80cb_1_" + (str(i)).encode() + + def cb_2(cb): + r = cb(20) + assert isinstance(r, bytes) + return b"\x80cb_2_" + r + + def cb_3(cb): + r = cb(cb_1) + assert isinstance(r, bytes) + return b"\x80cb_3_" + r + + def cb_2s(cb): + r = cb(20) + assert isinstance(r, str) # Passing bytes is unsupported (missing feature). + return b"\x80cb_2_" + r.encode() + + def cb_4(cb): + r = cb(cb_2s) + assert isinstance(r, bytes) + return b"\x80cb_4_" + r + + assert m.call_level_1_callback_si_b(cb_1) == b"\x80cb_1_1001" + assert m.call_level_2_callback_si_b(cb_2) == b"\x80cb_2_level_0_si_20" + assert m.call_level_3_callback_si_b(cb_3) == b"\x80cb_3_\x80cb_1_1001" + assert m.call_level_4_callback_si_b(cb_4) == b"\x80cb_4_\x80cb_2_level_0_si_20" + + +def test_nested_callbacks_is_s(): + def cb_1(s): + assert isinstance(s, str) + return 10000 + int(s) + + def cb_2(cb): + return 20000 + cb("20") + + def cb_3(cb): + return 30000 + cb(cb_1) + + def cb_4(cb): + return 40000 + cb(cb_2) + + assert m.call_level_1_callback_is_s(cb_1) == 10101 + assert m.call_level_2_callback_is_s(cb_2) == 20120 + assert m.call_level_3_callback_is_s(cb_3) == 40101 + assert m.call_level_4_callback_is_s(cb_4) == 60120 + + +def test_nested_callbacks_is_b(): + def cb_1(s): + assert isinstance(s, bytes) + return 10000 + int(s) + + def cb_2(cb): + return 20000 + cb(b"20") + + def cb_1s(s): + assert isinstance(s, str) # Passing bytes is unsupported (missing feature). + return 10000 + int(s) + + def cb_3(cb): + return 30000 + cb(cb_1s) + + def cb_4(cb): + return 40000 + cb(cb_2) + + assert m.call_level_1_callback_is_b(cb_1) == 10101 + assert m.call_level_2_callback_is_b(cb_2) == 20120 + assert m.call_level_3_callback_is_b(cb_3) == 40101 + assert m.call_level_4_callback_is_b(cb_4) == 60120