Skip to content

Commit 1f63796

Browse files
authored
make diagnostics describe how to fix the problem (#1627)
1 parent ac2d378 commit 1f63796

5 files changed

Lines changed: 180 additions & 43 deletions

File tree

include/stdexec/__detail/__diagnostics.hpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,155 @@ namespace stdexec {
103103
__mexception<_NOT_CALLABLE_<_Context>, _WITH_FUNCTION_<_Fun>, _WITH_ARGUMENTS_<_Args...>>;
104104
};
105105
} // namespace stdexec
106+
107+
////////////////////////////////////////////////////////////////////////////////
108+
#define STDEXEC_ERROR_ENABLE_SENDER_IS_FALSE \
109+
"\n" \
110+
"\n" \
111+
"The given type is not a sender because stdexec::enable_sender<Sender> is false. Either:\n" \
112+
"\n" \
113+
"1. Give the type a nested `::sender_concept` type that is an alias for `stdexec::sender_t`,\n" \
114+
" as in:\n" \
115+
"\n" \
116+
" class MySender\n" \
117+
" {\n" \
118+
" public:\n" \
119+
" using sender_concept = stdexec::sender_t;\n" \
120+
" ...\n" \
121+
" };\n" \
122+
"\n" \
123+
" or,\n" \
124+
"\n" \
125+
"2. Specialize the `stdexec::enable_sender` boolean trait for this type to true, as follows:\n" \
126+
"\n" \
127+
" class MySender\n" \
128+
" {\n" \
129+
" ...\n" \
130+
" };\n" \
131+
"\n" \
132+
" template <>\n" \
133+
" inline constexpr bool stdexec::enable_sender<MySender> = true;\n"
134+
135+
////////////////////////////////////////////////////////////////////////////////
136+
#define STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES \
137+
"\n" \
138+
"\n" \
139+
"The sender type was not able to report its completion signatures when asked.\n" \
140+
"This is either because it lacks the necessary member function, or because the\n" \
141+
"member function was ill-formed.\n" \
142+
"\n" \
143+
"A sender can declare its completion signatures in one of two ways:\n" \
144+
"\n" \
145+
"1. By defining a nested type alias named `completion_signatures` that is a\n" \
146+
" specialization of `stdexec::completion_signatures<...>`, as follows:\n" \
147+
"\n" \
148+
" class MySender\n" \
149+
" {\n" \
150+
" public:\n" \
151+
" using sender_concept = stdexec::sender_t;\n" \
152+
" using completion_signatures = stdexec::completion_signatures<\n" \
153+
" // This sender can complete successfully with an int and a float...\n" \
154+
" stdexec::set_value_t(int, float),\n" \
155+
" // ... or in error with an exception_ptr\n" \
156+
" stdexec::set_error_t(std::exception_ptr)>;\n" \
157+
" ...\n" \
158+
" };\n" \
159+
"\n" \
160+
" or,\n" \
161+
"\n" \
162+
"2. By defining a member function named `get_completion_signatures` that returns\n" \
163+
" a specialization of `stdexec::completion_signatures<...>`, as follows:\n" \
164+
"\n" \
165+
" class MySender\n" \
166+
" {\n" \
167+
" public:\n" \
168+
" using sender_concept = stdexec::sender_t;\n" \
169+
"\n" \
170+
" template <class... _Env>\n" \
171+
" auto get_completion_signatures(_Env&&...) -> stdexec::completion_signatures<\n" \
172+
" // This sender can complete successfully with an int and a float...\n" \
173+
" stdexec::set_value_t(int, float),\n" \
174+
" // ... or in error with a std::exception_ptr.\n" \
175+
" stdexec::set_error_t(std::exception_ptr)>\n" \
176+
" {\n" \
177+
" return {};\n" \
178+
" }\n" \
179+
" ...\n" \
180+
" };\n"
181+
182+
////////////////////////////////////////////////////////////////////////////////
183+
#define STDEXEC_ERROR_GET_COMPLETION_SIGNATURES_RETURNED_AN_ERROR \
184+
"\n" \
185+
"\n" \
186+
"Trying to compute the sender's completion signatures resulted in an error. See\n" \
187+
"the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n"
188+
189+
#define STDEXEC_ERROR_GET_COMPLETION_SIGNATURES_HAS_INVALID_RETURN_TYPE \
190+
"\n" \
191+
"\n" \
192+
"The member function `get_completion_signatures` of the sender returned an\n" \
193+
"invalid type.\n" \
194+
"\n" \
195+
"A sender's `get_completion_signatures` function must return a specialization of\n" \
196+
"`stdexec::completion_signatures<...>`, as follows:\n" \
197+
"\n" \
198+
" class MySender\n" \
199+
" {\n" \
200+
" public:\n" \
201+
" using sender_concept = stdexec::sender_t;\n" \
202+
"\n" \
203+
" template <class... _Env>\n" \
204+
" auto get_completion_signatures(_Env&&...) -> stdexec::completion_signatures<\n" \
205+
" // This sender can complete successfully with an int and a float...\n" \
206+
" stdexec::set_value_t(int, float),\n" \
207+
" // ... or in error with a std::exception_ptr.\n" \
208+
" stdexec::set_error_t(std::exception_ptr)>\n" \
209+
" {\n" \
210+
" return {};\n" \
211+
" }\n" \
212+
" ...\n" \
213+
" };\n"
214+
215+
////////////////////////////////////////////////////////////////////////////////
216+
#define STDEXEC_ERROR_CANNOT_CONNECT_SENDER_TO_RECEIVER \
217+
"\n" \
218+
"A sender must provide a `connect` member function that takes a receiver as an\n" \
219+
"argument and returns an object whose type satisfies `stdexec::operation_state`,\n" \
220+
"as shown below:\n" \
221+
"\n" \
222+
" class MySender\n" \
223+
" {\n" \
224+
" public:\n" \
225+
" using sender_concept = stdexec::sender_t;\n" \
226+
" using completion_signatures = stdexec::completion_signatures<stdexec::set_value_t()>;\n" \
227+
"\n" \
228+
" template <class Receiver>\n" \
229+
" struct MyOpState\n" \
230+
" {\n" \
231+
" using operation_state_concept = stdexec::operation_state_t;\n" \
232+
"\n" \
233+
" void start() noexcept\n" \
234+
" {\n" \
235+
" // Start the operation, which will eventually complete and send its\n" \
236+
" // result to rcvr_;\n" \
237+
" }\n" \
238+
"\n" \
239+
" Receiver rcvr_;\n" \
240+
" };\n" \
241+
"\n" \
242+
" template <stdexec::receiver Receiver>\n" \
243+
" auto connect(Receiver rcvr) -> MyOpState<Receiver>\n" \
244+
" {\n" \
245+
" return MyOpState<Receiver>{std::move(rcvr)};\n" \
246+
" }\n" \
247+
"\n" \
248+
" ...\n" \
249+
" };\n"
250+
251+
////////////////////////////////////////////////////////////////////////////////
252+
#define STDEXEC_ERROR_SYNC_WAIT_CANNOT_CONNECT_SENDER_TO_RECEIVER \
253+
"\n" \
254+
"\n" \
255+
"The sender passed to `stdexec::sync_wait()` does not have a `connect`\n" \
256+
"member function that accepts sync_wait's " \
257+
"receiver.\n" STDEXEC_ERROR_CANNOT_CONNECT_SENDER_TO_RECEIVER

include/stdexec/__detail/__receivers.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ namespace stdexec {
178178
auto __try_completions(completion_signatures<_Sigs...> *) -> decltype((
179179
__msuccess(),
180180
...,
181-
__detail::__try_completion<_Receiver>(static_cast<_Sigs *>(nullptr))));
181+
__detail::__try_completion<__decay_t<_Receiver>>(static_cast<_Sigs *>(nullptr))));
182182
} // namespace __detail
183183

184184
template <class _Receiver, class _Completions>
185185
concept receiver_of = receiver<_Receiver> && requires(_Completions *__completions) {
186-
{ __detail::__try_completions<__decay_t<_Receiver>>(__completions) } -> __ok;
186+
{ __detail::__try_completions<_Receiver>(__completions) } -> __ok;
187187
};
188188

189189
template <class _Receiver, class _Sender>

include/stdexec/__detail/__senders.hpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ namespace stdexec {
206206
return true;
207207
}
208208

209+
template <class _OpState>
210+
static constexpr void __check_operation_state() noexcept {
211+
static_assert(operation_state<_OpState>, STDEXEC_ERROR_CANNOT_CONNECT_SENDER_TO_RECEIVER);
212+
}
213+
209214
template <class _Sender, class _Receiver>
210215
static constexpr auto __select_impl() noexcept {
211216
using _Domain = __late_domain_of_t<_Sender, env_of_t<_Receiver>>;
@@ -222,31 +227,22 @@ namespace stdexec {
222227

223228
if constexpr (__with_static_member<_TfxSender, _Receiver>) {
224229
using _Result = __static_member_result_t<_TfxSender, _Receiver>;
225-
static_assert(
226-
operation_state<_Result>,
227-
"Sender::connect(sender, receiver) must return a type that "
228-
"satisfies the operation_state concept");
230+
__check_operation_state<_Result>();
229231
constexpr bool _Nothrow = _NothrowTfxSender
230232
&& noexcept(
231233
__declval<_TfxSender>()
232234
.connect(__declval<_TfxSender>(), __declval<_Receiver>()));
233235
return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr);
234236
} else if constexpr (__with_member<_TfxSender, _Receiver>) {
235237
using _Result = __member_result_t<_TfxSender, _Receiver>;
236-
static_assert(
237-
operation_state<_Result>,
238-
"sender.connect(receiver) must return a type that "
239-
"satisfies the operation_state concept");
238+
__check_operation_state<_Result>();
240239
constexpr bool _Nothrow = _NothrowTfxSender
241240
&& noexcept(__declval<_TfxSender>()
242241
.connect(__declval<_Receiver>()));
243242
return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr);
244243
} else if constexpr (__with_tag_invoke<_TfxSender, _Receiver>) {
245244
using _Result = tag_invoke_result_t<connect_t, _TfxSender, _Receiver>;
246-
static_assert(
247-
operation_state<_Result>,
248-
"stdexec::connect(sender, receiver) must return a type that "
249-
"satisfies the operation_state concept");
245+
__check_operation_state<_Result>();
250246
constexpr bool _Nothrow = _NothrowTfxSender
251247
&& nothrow_tag_invocable<connect_t, _TfxSender, _Receiver>;
252248
return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr);

0 commit comments

Comments
 (0)