Skip to content

Commit 95c342e

Browse files
authored
Improve error message if you co_await a non-awaitable (#702)
1 parent ab89b9f commit 95c342e

File tree

2 files changed

+79
-152
lines changed

2 files changed

+79
-152
lines changed

strings/base_coroutine_threadpool.h

Lines changed: 19 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -107,115 +107,41 @@ namespace winrt::impl
107107
}
108108

109109
template <typename T>
110-
class has_awaitable_member
110+
class awaiter_finder
111111
{
112-
template <typename U, typename = decltype(std::declval<U>().await_ready())> static constexpr bool get_value(int) { return true; }
113-
template <typename> static constexpr bool get_value(...) { return false; }
112+
template <typename U, typename = decltype(std::declval<U>().await_ready())> static constexpr bool find_awaitable_member(int) { return true; }
113+
template <typename> static constexpr bool find_awaitable_member(...) { return false; }
114114

115-
public:
116-
117-
static constexpr bool value = get_value<T>(0);
118-
};
115+
template <typename U, typename = decltype(std::declval<U>().operator co_await())> static constexpr bool find_co_await_member(int) { return true; }
116+
template <typename> static constexpr bool find_co_await_member(...) { return false; }
119117

120-
template <typename T>
121-
class has_awaitable_free
122-
{
123-
template <typename U, typename = decltype(await_ready(std::declval<U>()))> static constexpr bool get_value(int) { return true; }
124-
template <typename> static constexpr bool get_value(...) { return false; }
118+
template <typename U, typename = decltype(operator co_await(std::declval<U>()))> static constexpr bool find_co_await_free(int) { return true; }
119+
template <typename> static constexpr bool find_co_await_free(...) { return false; }
125120

126121
public:
127122

128-
static constexpr bool value = get_value<T>(0);
123+
static constexpr bool has_awaitable_member = find_awaitable_member<T>(0);
124+
static constexpr bool has_co_await_member = find_co_await_member<T&&>(0);
125+
static constexpr bool has_co_await_free = find_co_await_free<T&&>(0);
129126
};
130127

131128
template <typename T>
132-
struct free_await_adapter_impl
129+
decltype(auto) get_awaiter(T&& value) noexcept
133130
{
134-
T&& awaitable;
135-
136-
bool ready()
137-
{
138-
return await_ready(awaitable);
139-
}
140-
141-
template <typename U>
142-
auto suspend(std::experimental::coroutine_handle<U> handle)
131+
if constexpr (awaiter_finder<T>::has_co_await_member)
143132
{
144-
return await_suspend(awaitable, handle);
133+
static_assert(!awaiter_finder<T>::has_co_await_free, "Ambiguous operator co_await (as both member and free function).");
134+
return static_cast<T&&>(value).operator co_await();
145135
}
146-
147-
auto resume()
136+
else if constexpr (awaiter_finder<T>::has_co_await_free)
148137
{
149-
return await_resume(awaitable);
138+
return operator co_await(static_cast<T&&>(value));
150139
}
151-
};
152-
153-
template <typename T>
154-
struct free_await_adapter
155-
{
156-
T&& awaitable;
157-
158-
bool await_ready()
159-
{
160-
return free_await_adapter_impl<T>{ static_cast<T&&>(awaitable) }.ready();
161-
}
162-
163-
template <typename U>
164-
auto await_suspend(std::experimental::coroutine_handle<U> handle)
165-
{
166-
return free_await_adapter_impl<T>{ static_cast<T&&>(awaitable) }.suspend(handle);
167-
}
168-
169-
decltype(auto) await_resume()
170-
{
171-
return free_await_adapter_impl<T>{ static_cast<T&&>(awaitable) }.resume();
172-
}
173-
};
174-
175-
template <typename T>
176-
struct member_await_adapter
177-
{
178-
T&& awaitable;
179-
180-
bool await_ready()
181-
{
182-
return awaitable.await_ready();
183-
}
184-
185-
template <typename U>
186-
auto await_suspend(std::experimental::coroutine_handle<U> handle)
187-
{
188-
return awaitable.await_suspend(handle);
189-
}
190-
191-
decltype(auto) await_resume()
140+
else
192141
{
193-
return awaitable.await_resume();
142+
static_assert(awaiter_finder<T>::has_awaitable_member, "Not an awaitable type");
143+
return static_cast<T&&>(value);
194144
}
195-
};
196-
197-
template <typename T>
198-
auto get_awaiter(T&& value) noexcept -> decltype(static_cast<T&&>(value).operator co_await())
199-
{
200-
return static_cast<T&&>(value).operator co_await();
201-
}
202-
203-
template <typename T>
204-
auto get_awaiter(T&& value) noexcept -> decltype(operator co_await(static_cast<T&&>(value)))
205-
{
206-
return operator co_await(static_cast<T&&>(value));
207-
}
208-
209-
template <typename T, std::enable_if_t<has_awaitable_member<T>::value, int> = 0>
210-
auto get_awaiter(T&& value) noexcept
211-
{
212-
return member_await_adapter<T>{ static_cast<T&&>(value) };
213-
}
214-
215-
template <typename T, std::enable_if_t<has_awaitable_free<T>::value, int> = 0>
216-
auto get_awaiter(T&& value) noexcept
217-
{
218-
return free_await_adapter<T>{ static_cast<T&&>(value) };
219145
}
220146

221147
template <typename T>

test/test/notify_awaiter.cpp

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,91 @@ using namespace Windows::Foundation;
55

66
namespace
77
{
8-
struct free_awaitable
9-
{
10-
};
11-
bool await_ready(free_awaitable)
12-
{
13-
return true;
14-
}
15-
void await_suspend(free_awaitable, std::experimental::coroutine_handle<>)
16-
{
17-
}
18-
void await_resume(free_awaitable)
19-
{
8+
using std::experimental::suspend_never;
209

21-
}
22-
23-
struct member_awaitable
10+
// Never suspends.
11+
// Allows copying, but asserts if you try.
12+
struct suspend_never_but_assert_if_copied : suspend_never
2413
{
25-
bool await_ready()
14+
suspend_never_but_assert_if_copied() = default;
15+
suspend_never_but_assert_if_copied(suspend_never_but_assert_if_copied const&)
2616
{
27-
return true;
17+
REQUIRE(false);
2818
}
29-
void await_suspend(std::experimental::coroutine_handle<>)
19+
};
20+
21+
// The bad_awaiter asserts if it ever gets used.
22+
// Use it for ambiguous alternatives we don't want to pick.
23+
struct bad_awaiter : suspend_never
24+
{
25+
void await_resume() const noexcept
3026
{
27+
REQUIRE(false);
3128
}
32-
void await_resume()
33-
{
29+
};
3430

35-
}
31+
// If the only awaitable is the member awaitable, then use it.
32+
struct member_awaitable : suspend_never_but_assert_if_copied
33+
{
3634
};
3735

38-
struct free_operator_awaitable
36+
// Should pick the free operator co_await over the member awaitable.
37+
struct free_operator_awaitable : bad_awaiter
3938
{
4039
};
4140
auto operator co_await(free_operator_awaitable)
4241
{
43-
struct awaitable
44-
{
45-
bool await_ready()
46-
{
47-
return true;
48-
}
49-
void await_suspend(std::experimental::coroutine_handle<>)
50-
{
51-
}
52-
void await_resume()
53-
{
54-
}
55-
};
56-
return awaitable{};
42+
return suspend_never_but_assert_if_copied{};
5743
}
5844

59-
struct member_operator_awaitable
45+
// Should pick the member operator co_await over the member awaitable.
46+
struct member_operator_awaitable : bad_awaiter
6047
{
6148
auto operator co_await()
6249
{
63-
struct awaitable
64-
{
65-
bool await_ready()
66-
{
67-
return true;
68-
}
69-
void await_suspend(std::experimental::coroutine_handle<>)
70-
{
71-
}
72-
void await_resume()
73-
{
74-
}
75-
};
76-
return awaitable{};
50+
return suspend_never_but_assert_if_copied{};
7751
}
7852
};
7953

80-
struct no_copy_awaitable
54+
// Verify that we can await non-copyable objects.
55+
struct no_copy_awaitable : suspend_never
8156
{
8257
no_copy_awaitable() = default;
8358
no_copy_awaitable(no_copy_awaitable const&) = delete;
59+
};
8460

85-
bool await_ready()
61+
// operator co_await takes precedence over member awaitable.
62+
struct ambiguous_awaitable1 : bad_awaiter
63+
{
64+
auto operator co_await()
8665
{
87-
return true;
66+
return suspend_never_but_assert_if_copied{};
8867
}
89-
void await_suspend(std::experimental::coroutine_handle<>)
68+
};
69+
70+
// This awaitable supports both member co_await
71+
// and free co_await, and the free co_await is a
72+
// better match if invoked on an lvalue. We don't try
73+
// to support this case. We just declare it as ambiguous.
74+
struct ambiguous_awaitable2 : bad_awaiter
75+
{
76+
auto operator co_await() const
9077
{
78+
return bad_awaiter{};
9179
}
92-
void await_resume()
93-
{
80+
};
81+
suspend_never_but_assert_if_copied operator co_await(ambiguous_awaitable2&)
82+
{
83+
return {};
84+
}
9485

86+
// We invoke this on an lvalue, so the member co_await is unavailable.
87+
// Verify that the unavailable co_await is ignored.
88+
struct ambiguous_awaitable3 : suspend_never_but_assert_if_copied
89+
{
90+
auto operator co_await()&&
91+
{
92+
return bad_awaiter{};
9593
}
9694
};
9795

@@ -120,18 +118,21 @@ namespace
120118

121119
static std::vector<std::pair<void const*, notification>> watcher;
122120
static handle start_racing{ CreateEventW(nullptr, true, false, nullptr) };
123-
constexpr size_t test_suspension_points = 12;
121+
constexpr size_t test_suspension_points = 13;
124122

125123
IAsyncAction Async()
126124
{
127125
co_await resume_on_signal(start_racing.get());
128126
co_await resume_background();
129127
co_await resume_background();
130-
co_await free_awaitable{};
131128
co_await member_awaitable{};
132129
co_await free_operator_awaitable{};
133130
co_await member_operator_awaitable{};
134131
co_await no_copy_awaitable{};
132+
co_await ambiguous_awaitable1{};
133+
// co_await ambiguous_awaitable2{}; // does not compile
134+
ambiguous_awaitable3 awaitable3;
135+
co_await awaitable3;
135136
co_await AsyncAction();
136137
co_await AsyncActionWithProgress();
137138
co_await AsyncOperation();

0 commit comments

Comments
 (0)