diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 8539d45ca..1e91584ff 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -617,6 +617,12 @@ namespace STDEXEC # define STDEXEC_NO_STDCPP_PACK_INDEXING() 1 #endif // no pack indexing +#if __cpp_impl_reflection >= 202506L && __cpp_lib_reflection >= 202506L +# define STDEXEC_NO_STDCPP_REFLECTION() 0 +#else +# define STDEXEC_NO_STDCPP_REFLECTION() 1 +#endif + #if STDEXEC_HAS_FEATURE(thread_sanitizer) || defined(__SANITIZE_THREAD__) # define STDEXEC_TSAN() 1 #else diff --git a/include/stdexec/__detail/__get_completion_signatures.hpp b/include/stdexec/__detail/__get_completion_signatures.hpp index b9f55229e..1b3f0f2b0 100644 --- a/include/stdexec/__detail/__get_completion_signatures.hpp +++ b/include/stdexec/__detail/__get_completion_signatures.hpp @@ -35,6 +35,10 @@ import stdexec; # include "__tag_invoke.hpp" # include "__tuple.hpp" // IWYU pragma: keep for __tuple +# if !STDEXEC_NO_STDCPP_REFLECTION() +# include +# endif + # include "__prologue.hpp" namespace STDEXEC @@ -62,6 +66,16 @@ namespace STDEXEC struct _A_GET_COMPLETION_SIGNATURES_CUSTOMIZATION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION; +# if !STDEXEC_NO_STDCPP_REFLECTION() + STDEXEC_MODULE_EXPORT + template + consteval std::meta::info get_completion_signatures_type(); + + STDEXEC_MODULE_EXPORT + template + consteval std::meta::info get_completion_signatures_type(); +# endif + namespace __cmplsigs { # define STDEXEC_GET_COMPLSIGS(...) \ @@ -162,6 +176,28 @@ namespace STDEXEC template concept __with_co_await = __awaitable<_Sender, __detail::__promise<_Env>...>; +# if !STDEXEC_NO_STDCPP_REFLECTION() + template + concept __has_get_completion_signatures_type = requires { + { + STDEXEC_REMOVE_REFERENCE(_Sender) + ::template get_completion_signatures_type<_Sender, _Env...>() + } -> __std::same_as; + }; + + template + consteval bool __nothrow_get_completion_signatures_type() noexcept + try + { + (void) STDEXEC::get_completion_signatures_type<_Sender, _Env...>(); + return true; + } + catch (...) + { + return false; + } +# endif + template concept __with = __with_legacy_static_member<_Sender, _Env> // || __with_legacy_member<_Sender, _Env> // @@ -170,7 +206,11 @@ namespace STDEXEC || __with_consteval_static_member<_Sender> // || __with_legacy_tag_invoke<_Sender, _Env> // || __with_legacy_non_dependent_tag_invoke<_Sender, _Env> // - || __with_co_await<_Sender, _Env>; + || __with_co_await<_Sender, _Env> +# if !STDEXEC_NO_STDCPP_REFLECTION() + || __has_get_completion_signatures_type<_Sender, _Env> +# endif + ; } // namespace __cmplsigs template @@ -354,7 +394,26 @@ namespace STDEXEC template consteval auto get_completion_signatures() { - return __cmplsigs::__get_completion_signatures_helper<_Sender>(); +# if !STDEXEC_NO_STDCPP_REFLECTION() + if constexpr (__cmplsigs::__has_get_completion_signatures_type<_Sender>) + { + if constexpr (__cmplsigs::__nothrow_get_completion_signatures_type<_Sender>()) + { + constexpr std::meta::info __completions = + STDEXEC::get_completion_signatures_type<_Sender>(); + return typename[:__completions:]{}; + } + else + { + (void) STDEXEC::get_completion_signatures_type<_Sender>(); + return completion_signatures<>{}; + } + } + else +# endif + { + return __cmplsigs::__get_completion_signatures_helper<_Sender>(); + } } //! @brief Overload of @ref get_completion_signatures that takes an @@ -377,9 +436,63 @@ namespace STDEXEC { using __new_sndr_t = transform_sender_result_t<_Sender, _Env>; static_assert(!__merror<__new_sndr_t>); - return __cmplsigs::__get_completion_signatures_helper<__new_sndr_t, _Env>(); +# if !STDEXEC_NO_STDCPP_REFLECTION() + if constexpr (__cmplsigs::__has_get_completion_signatures_type<__new_sndr_t, _Env>) + { + if constexpr (__cmplsigs::__nothrow_get_completion_signatures_type<_Sender, _Env>()) + { + constexpr std::meta::info __completions = + STDEXEC::get_completion_signatures_type<_Sender, _Env>(); + return typename[:__completions:]{}; + } + else + { + (void) STDEXEC::get_completion_signatures_type<_Sender, _Env>(); + return completion_signatures<>{}; + } + } + else +# endif + { + return __cmplsigs::__get_completion_signatures_helper<__new_sndr_t, _Env>(); + } } +# if !STDEXEC_NO_STDCPP_REFLECTION() + STDEXEC_MODULE_EXPORT + template + consteval std::meta::info get_completion_signatures_type() + { + if constexpr (__cmplsigs::__has_get_completion_signatures_type<_Sender>) + { + return STDEXEC_REMOVE_REFERENCE(_Sender)::template get_completion_signatures_type<_Sender>(); + } + else + { + auto __completions = STDEXEC::get_completion_signatures<_Sender>(); + return ^^decltype(__completions); + } + } + + STDEXEC_MODULE_EXPORT + template + consteval std::meta::info get_completion_signatures_type() + { + using __new_sndr_t = transform_sender_result_t<_Sender, _Env>; + static_assert(!__merror<__new_sndr_t>); + if constexpr (__cmplsigs::__has_get_completion_signatures_type<__new_sndr_t, _Env>) + { + return STDEXEC_REMOVE_REFERENCE( + __new_sndr_t)::template get_completion_signatures_type<__new_sndr_t, _Env>(); + } + else + { + auto __completions = STDEXEC::get_completion_signatures<_Sender, _Env>(); + return ^^decltype(__completions); + } + } +# endif + // Legacy interface: STDEXEC_MODULE_EXPORT_AUTHORING template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2725a8fba..ba57f7a8b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,6 +68,7 @@ set(stdexec_test_sources stdexec/detail/test_common_domain.cpp stdexec/detail/test_completion_signatures.cpp stdexec/detail/test_demangle.cpp + stdexec/detail/test_get_completion_signatures.cpp stdexec/detail/test_intrusive_mpsc_queue.cpp stdexec/detail/test_utility.cpp stdexec/queries/test_env.cpp diff --git a/test/stdexec/detail/test_get_completion_signatures.cpp b/test/stdexec/detail/test_get_completion_signatures.cpp new file mode 100644 index 000000000..0d17ea2ca --- /dev/null +++ b/test/stdexec/detail/test_get_completion_signatures.cpp @@ -0,0 +1,248 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#if !STDEXEC_NO_STDCPP_REFLECTION() +# include +#endif + +namespace ex = STDEXEC; + +namespace +{ + using independent_completions = ex::completion_signatures; + using dependent_completions = ex::completion_signatures; + + struct test_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval auto get_completion_signatures() noexcept + { + if constexpr (sizeof...(Env) == 0) + { + return independent_completions{}; + } + else + { + return dependent_completions{}; + } + } + }; + +#if !STDEXEC_NO_STDCPP_REFLECTION() + template + consteval bool completion_signature_protocols_agree() + { + return std::meta::is_same_type(ex::get_completion_signatures_type(), + ^^decltype(ex::get_completion_signatures())); + } + + template + consteval bool get_completion_signatures_type_throws() noexcept + try + { + (void) ex::get_completion_signatures_type(); + return false; + } + catch (...) + { + return true; + } + + struct throwing_legacy_test_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval auto get_completion_signatures() + { + throw std::meta::exception("Unable to compute completion signatures.", + ^^throwing_legacy_test_sender); + return ex::completion_signatures<>{}; + } + }; + + struct reflection_test_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval std::meta::info get_completion_signatures_type() noexcept + { + if constexpr (sizeof...(Env) == 0) + { + return ^^independent_completions; + } + else + { + return ^^dependent_completions; + } + } + }; + + struct transformed_reflection_test_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval std::meta::info get_completion_signatures_type() noexcept + { + return ^^dependent_completions; + } + }; + + struct throwing_reflection_test_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval std::meta::info get_completion_signatures_type() + { + throw std::meta::exception("Unable to compute completion signatures.", + ^^throwing_reflection_test_sender); + } + }; + + struct reflection_test_domain + { + template + auto transform_sender(ex::start_t, reflection_test_sender, Env const &) const + -> transformed_reflection_test_sender + { + return {}; + } + }; + + using reflection_test_env = ex::prop; + + struct throwing_reflection_test_domain + { + template + auto transform_sender(ex::start_t, reflection_test_sender, Env const &) const + -> throwing_reflection_test_sender + { + return {}; + } + }; + + using throwing_reflection_test_env = ex::prop; +#endif + + TEST_CASE("get_completion_signatures queries a sender without an environment", + "[detail][get_completion_signatures]") + { + STATIC_REQUIRE(std::same_as()), + independent_completions>); +#if !STDEXEC_NO_STDCPP_REFLECTION() + STATIC_REQUIRE(completion_signature_protocols_agree()); +#endif + } + + TEST_CASE("get_completion_signatures queries a sender in an environment", + "[detail][get_completion_signatures]") + { + STATIC_REQUIRE(std::same_as>()), + dependent_completions>); +#if !STDEXEC_NO_STDCPP_REFLECTION() + STATIC_REQUIRE(completion_signature_protocols_agree>()); +#endif + } + + TEST_CASE("get_completion_signatures supports the legacy function-call interface", + "[detail][get_completion_signatures]") + { + STATIC_REQUIRE(std::same_as{})), + dependent_completions>); +#if !STDEXEC_NO_STDCPP_REFLECTION() + STATIC_REQUIRE(completion_signature_protocols_agree>()); +#endif + } + +#if !STDEXEC_NO_STDCPP_REFLECTION() + TEST_CASE("get_completion_signatures_type reflects the legacy query result", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE(std::meta::is_same_type(ex::get_completion_signatures_type(), + ^^independent_completions)); + STATIC_REQUIRE( + std::meta::is_same_type(ex::get_completion_signatures_type>(), + ^^dependent_completions)); + STATIC_REQUIRE(completion_signature_protocols_agree()); + STATIC_REQUIRE(completion_signature_protocols_agree>()); + } + + TEST_CASE("get_completion_signatures_type invokes the legacy query", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE(get_completion_signatures_type_throws()); + STATIC_REQUIRE(get_completion_signatures_type_throws>()); + } + + TEST_CASE("detect whether get_completion_signatures_type throws", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE( + ex::__cmplsigs::__nothrow_get_completion_signatures_type()); + STATIC_REQUIRE_FALSE( + ex::__cmplsigs::__nothrow_get_completion_signatures_type()); + STATIC_REQUIRE_FALSE( + ex::__cmplsigs::__nothrow_get_completion_signatures_type()); + STATIC_REQUIRE( + std::same_as()), + ex::completion_signatures<>>); + STATIC_REQUIRE( + std::same_as()), + ex::completion_signatures<>>); + } + + TEST_CASE("get_completion_signatures_type queries a reflection-native sender without an " + "environment", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE( + std::meta::is_same_type(ex::get_completion_signatures_type(), + ^^independent_completions)); + STATIC_REQUIRE(completion_signature_protocols_agree()); + } + + TEST_CASE("get_completion_signatures_type queries a reflection-native sender in an environment", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE(std::meta::is_same_type( + ex::get_completion_signatures_type>(), + ^^dependent_completions)); + STATIC_REQUIRE(completion_signature_protocols_agree>()); + } + + TEST_CASE("get_completion_signatures_type queries the transformed sender", + "[detail][get_completion_signatures_type][reflection]") + { + STATIC_REQUIRE(std::meta::is_same_type( + ex::get_completion_signatures_type(), + ^^dependent_completions)); + STATIC_REQUIRE( + completion_signature_protocols_agree()); + } +#endif +} // namespace