Commit 6efc255f by Iain Sandoe

coroutines: Handle non-method promise expressions [PR95519]

The PR  points out that the standard does not restrict promise
expressions to methods, but the current implementation does.

The patch factors out the building of a general promise expression,
and then uses it in a fairly mechanical replacement of each case
that we need such an expressions.

This extends the handling for p.xxxxxx() expressions to cover the
cases where the promise member is some form callable.

Tests are added for each of the promise expressions.

It's somewhat tortuous to find good uses for this for the
get-return-object and get-return-object-on-allocation-failure
cases, but they are included anyway.

	Backported from e74c76073092f4715007584edb1fe6b7a17274db and 31419a80b6bf84b1bf3bcb2489b2e660563f0dfe

gcc/cp/ChangeLog:

	PR c++/95519
	* coroutines.cc (struct coroutine_info):Add a field
	to hold computed p.return_void expressions.
	(coro_build_promise_expression): New.
	(get_coroutine_return_void_expr): New.
	(finish_co_yield_expr): Build the promise expression
	using coro_build_promise_expression.
	(finish_co_return_stmt): Likewise.
	(build_init_or_final_await): Likewise.
	(morph_fn_to_coro): Likewise, for several cases.

gcc/testsuite/ChangeLog:

	PR c++/95519
	* g++.dg/coroutines/torture/pr95519-00-return_void.C: New test.
	* g++.dg/coroutines/torture/pr95519-01-initial-suspend.C: New test.
	* g++.dg/coroutines/torture/pr95519-02-final_suspend.C: New test.
	* g++.dg/coroutines/torture/pr95519-03-return-value.C: New test.
	* g++.dg/coroutines/torture/pr95519-04-yield-value.C: New test.
	* g++.dg/coroutines/torture/pr95519-05-gro.C: New test.
	* g++.dg/coroutines/torture/pr95519-06-grooaf.C: New test.
	* g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C: New test.
parent e6d3351d
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_never initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
void unhandled_exception() const noexcept {}
};
int called_rv_op = 0;
struct rv
{
void operator ()(){
PRINT("call to operator ");
called_rv_op++;
}
};
struct pt_c : pt_b
{
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
rv return_void;
};
int called_lambda = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto return_void
= []{ PRINT("call to lambda "); called_lambda++; };
};
template <> struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t foo ()
{
co_return;
}
template <> struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t bar ()
{
co_return;
}
int main ()
{
foo ();
bar ();
if (called_rv_op != 1 || called_lambda != 1)
{
PRINT ("Failed to call one of the return_void cases");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_never final_suspend() const noexcept { return {}; }
constexpr void return_void () noexcept {};
void unhandled_exception() const noexcept {}
};
int called_is_op = 0;
struct is
{
std::suspend_never operator ()() noexcept {
PRINT("call to operator IS");
called_is_op++;
return {};
}
};
struct pt_c : pt_b
{
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
is initial_suspend;
};
int called_lambda = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto initial_suspend
= []() noexcept {
PRINT("call to lambda IS");
called_lambda++;
return std::suspend_never{};
};
};
template <>
struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t foo ()
{
co_return;
}
template <>
struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t bar ()
{
co_return;
}
int main ()
{
foo ();
bar ();
if (called_is_op != 1 || called_lambda != 1)
{
PRINT ("Failed to call one of the initial_suspend cases");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_never initial_suspend() const noexcept { return {}; }
constexpr void return_void () noexcept {};
void unhandled_exception() const noexcept {}
};
int called_fs_op = 0;
struct fs
{
auto operator ()() noexcept {
PRINT("call to operator FS");
called_fs_op++;
return std::suspend_never{};
}
};
struct pt_c : pt_b
{
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
fs final_suspend;
};
int called_lambda = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
constexpr static auto final_suspend
= []() noexcept {
PRINT("call to lambda FS");
called_lambda++;
return std::suspend_never{};
};
};
template <>
struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t foo ()
{
co_return;
}
template <>
struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t bar ()
{
co_return;
}
int main ()
{
foo ();
bar ();
if (called_fs_op != 1 || called_lambda != 1)
{
PRINT ("Failed to call one of the final_suspend cases");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_never initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
void unhandled_exception() const noexcept {}
};
int called_rv_op = 0;
int v;
struct pt_c : pt_b
{
struct rv
{
void operator ()(int x){
PRINTF("call to operator ret val with %d\n", x);
called_rv_op++;
v = x;
// int val () { return x; }
}
};
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
rv return_value;
};
int called_lambda = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto return_value
= [] (int x) { PRINTF("call to lambda ret val %d\n", x); called_lambda++; v = x;};
};
template <> struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t foo ()
{
co_return 5;
}
template <> struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t bar ()
{
co_return 3;
}
int main ()
{
/* These 'coroutines' run to completion imediately, like a regular fn. */
foo ();
if (v != 5)
{
PRINT ("foo failed to set v");
abort ();
}
bar ();
if (v != 3)
{
PRINT ("bar failed to set v");
abort ();
}
if (called_rv_op != 1 || called_lambda != 1)
{
PRINT ("Failed to call one of the return_value cases");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_never initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
void unhandled_exception() const noexcept {}
constexpr static void return_void () noexcept {}
};
int called_yv_op = 0;
int v;
struct pt_c : pt_b
{
struct yv
{
auto operator ()(int x){
PRINTF("call to operator yield val with %d\n", x);
called_yv_op++;
v = x;
return std::suspend_never{};
}
};
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
yv yield_value;
};
int called_lambda = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto yield_value = [] (int x) -> std::suspend_never
{
PRINTF("call to lambda yield val %d\n", x);
called_lambda++;
v = x;
return {};
};
};
template <> struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t foo ()
{
co_yield 5;
}
template <> struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t bar ()
{
co_yield 3;
}
int main ()
{
/* These 'coroutines' run to completion imediately, like a regular fn. */
foo ();
if (v != 5)
{
PRINT ("foo failed to yield v");
abort ();
}
bar ();
if (v != 3)
{
PRINT ("bar failed to yield v");
abort ();
}
if (called_yv_op != 1 || called_lambda != 1)
{
PRINT ("Failed to call one of the yield_value cases");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
std::suspend_always initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
constexpr void return_void () noexcept {};
constexpr void unhandled_exception() const noexcept {}
};
int called_gro_op = 0;
template<typename R, typename HandleRef, typename ...T>
struct std::coroutine_traits<R, HandleRef, T...> {
struct pt_c;
using promise_type = pt_c;
struct pt_c : pt_b {
//using handle_t = std::coroutine_handle<pt_c>;
pt_c (HandleRef h, T ...args)
{ h = std::coroutine_handle<pt_c>::from_promise (*this);
PRINT ("Created Promise");
//g_promise = 1;
}
struct gro
{
auto operator ()() {
PRINT("call to operator ");
called_gro_op++;
}
};
gro get_return_object;
};
};
static void
foo (std::coroutine_handle<>& h)
{
co_return;
}
int main ()
{
std::coroutine_handle<> f;
foo (f);
if (f.done())
{
PRINT ("unexpected finished foo coro");
abort ();
}
f.resume();
if (!f.done())
{
PRINT ("expected foo to be finished");
abort ();
}
if (called_gro_op != 1)
{
PRINT ("Failed to call gro op");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
constexpr std::suspend_never initial_suspend() noexcept { return {}; }
constexpr std::suspend_never final_suspend() noexcept { return {}; }
constexpr void return_void () noexcept {}
constexpr void unhandled_exception() noexcept {}
};
int called_grooaf = 0;
struct pt_c : pt_b
{
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto get_return_object_on_allocation_failure
= []{ PRINT("call to lambda grooaf");
called_grooaf++; return std::coroutine_handle<pt_c>{};
};
/* Provide an operator new, that always fails. */
void *operator new (std::size_t sz) noexcept {
PRINT ("promise_type: used failing op new");
return nullptr;
}
};
template <> struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t
foo ()
{
co_return;
}
int main ()
{
foo ();
if (called_grooaf != 1)
{
PRINT ("Failed to call grooaf");
abort ();
}
}
// { dg-do run }
#include "../coro.h"
struct pt_b
{
constexpr std::suspend_never initial_suspend() const noexcept { return {}; }
constexpr std::suspend_never final_suspend() const noexcept { return {}; }
constexpr void return_void () {};
};
int called_ueh_op = 0;
struct ueh
{
void operator ()() noexcept {
PRINT("call to operator UEH");
called_ueh_op++;
}
};
struct pt_c : pt_b
{
using handle_t = std::coroutine_handle<pt_c>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
ueh unhandled_exception;
};
int lambda_ueh = 0;
struct pt_d : pt_b
{
using handle_t = std::coroutine_handle<pt_d>;
auto get_return_object() noexcept { return handle_t::from_promise(*this); }
static constexpr auto unhandled_exception
= [] () noexcept { PRINT("call to lambda UEH"); lambda_ueh++; };
};
template <>
struct std::coroutine_traits<pt_c::handle_t>
{ using promise_type = pt_c; };
static pt_c::handle_t
foo ()
{
throw ("foo");
co_return;
}
template <>
struct std::coroutine_traits<pt_d::handle_t>
{ using promise_type = pt_d; };
static pt_d::handle_t
bar ()
{
throw ("bar");
co_return;
}
int main ()
{
foo ();
bar ();
if (called_ueh_op != 1 || lambda_ueh != 1)
{
PRINT ("Failed to call one of the UEH cases");
abort ();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment