diff mbox series

[5/9,v2] c++, coroutines: Only allow void get_return_object if the ramp is void [PR100476].

Message ID 20240823183033.55719-1-iain@sandoe.co.uk
State New
Headers show
Series None | expand

Commit Message

Iain Sandoe Aug. 23, 2024, 6:30 p.m. UTC
Hi Jason

>>+  /* Check for a bad get return object type.  */
>>+  tree gro_return_type = FUNC_OR_METHOD_TYPE_P (TREE_TYPE (get_ro_meth))
>>+		       ? TREE_TYPE (TREE_TYPE (get_ro_meth))
>>+		       : TREE_TYPE (get_ro_meth);

>Is this to allow get_return_type to be a function-like-object?  If so, checking its TREE_TYPE won't give the return type of its op(). Shouldn't we just use TREE_TYPE (get_ro) once we have it?

Yes the idea is to try and test as much of the stuff that needs to be diagnosed
early, so that we do not need to tweak the input_pointer to get sensible messages.
However, in this case what you suggest will work fine - we can use error_at (....).
So now checking once we have the get_ro.

retested on x86_64-darwin, OK for trunk?
thanks
Iain

--- 8< ---

Require that the value returned by get_return_object is convertible to
the ramp return.  This means that the only time we allow a void
get_return_object, is when the ramp is also a void function.

We diagnose this early to allow us to exit the ramp build if the return
values are incompatible.

	PR c++/100476

gcc/cp/ChangeLog:

	* coroutines.cc
	(cp_coroutine_transform::build_ramp_function): Remove special
	handling of void get_return_object expressions.

gcc/testsuite/ChangeLog:

	* g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C:
	Adjust expected diagnostic.
	* g++.dg/coroutines/pr102489.C: Avoid void get_return_object.
	* g++.dg/coroutines/pr103868.C: Likewise.
	* g++.dg/coroutines/pr94879-folly-1.C: Likewise.
	* g++.dg/coroutines/pr94883-folly-2.C: Likewise.
	* g++.dg/coroutines/pr96749-2.C: Likewise.

Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
---
 gcc/cp/coroutines.cc                          | 47 +++++++++----------
 .../coro-bad-gro-01-void-gro-non-class-coro.C |  2 +-
 gcc/testsuite/g++.dg/coroutines/pr102489.C    |  2 +-
 gcc/testsuite/g++.dg/coroutines/pr103868.C    |  2 +-
 .../g++.dg/coroutines/pr94879-folly-1.C       |  3 +-
 .../g++.dg/coroutines/pr94883-folly-2.C       | 39 +++++++--------
 gcc/testsuite/g++.dg/coroutines/pr96749-2.C   |  2 +-
 7 files changed, 48 insertions(+), 49 deletions(-)

Comments

Jason Merrill Aug. 24, 2024, 3:14 a.m. UTC | #1
On 8/23/24 2:30 PM, Iain Sandoe wrote:
> Hi Jason
> 
>>> +  /* Check for a bad get return object type.  */
>>> +  tree gro_return_type = FUNC_OR_METHOD_TYPE_P (TREE_TYPE (get_ro_meth))
>>> +		       ? TREE_TYPE (TREE_TYPE (get_ro_meth))
>>> +		       : TREE_TYPE (get_ro_meth);
> 
>> Is this to allow get_return_type to be a function-like-object?  If so, checking its TREE_TYPE won't give the return type of its op(). Shouldn't we just use TREE_TYPE (get_ro) once we have it?
> 
> Yes the idea is to try and test as much of the stuff that needs to be diagnosed
> early, so that we do not need to tweak the input_pointer to get sensible messages.
> However, in this case what you suggest will work fine - we can use error_at (....).
> So now checking once we have the get_ro.
> 
> retested on x86_64-darwin, OK for trunk?

OK.

> thanks
> Iain
> 
> --- 8< ---
> 
> Require that the value returned by get_return_object is convertible to
> the ramp return.  This means that the only time we allow a void
> get_return_object, is when the ramp is also a void function.
> 
> We diagnose this early to allow us to exit the ramp build if the return
> values are incompatible.
> 
> 	PR c++/100476
> 
> gcc/cp/ChangeLog:
> 
> 	* coroutines.cc
> 	(cp_coroutine_transform::build_ramp_function): Remove special
> 	handling of void get_return_object expressions.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C:
> 	Adjust expected diagnostic.
> 	* g++.dg/coroutines/pr102489.C: Avoid void get_return_object.
> 	* g++.dg/coroutines/pr103868.C: Likewise.
> 	* g++.dg/coroutines/pr94879-folly-1.C: Likewise.
> 	* g++.dg/coroutines/pr94883-folly-2.C: Likewise.
> 	* g++.dg/coroutines/pr96749-2.C: Likewise.
> 
> Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
> ---
>   gcc/cp/coroutines.cc                          | 47 +++++++++----------
>   .../coro-bad-gro-01-void-gro-non-class-coro.C |  2 +-
>   gcc/testsuite/g++.dg/coroutines/pr102489.C    |  2 +-
>   gcc/testsuite/g++.dg/coroutines/pr103868.C    |  2 +-
>   .../g++.dg/coroutines/pr94879-folly-1.C       |  3 +-
>   .../g++.dg/coroutines/pr94883-folly-2.C       | 39 +++++++--------
>   gcc/testsuite/g++.dg/coroutines/pr96749-2.C   |  2 +-
>   7 files changed, 48 insertions(+), 49 deletions(-)
> 
> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
> index 2f128db42e1..3c0757403ce 100644
> --- a/gcc/cp/coroutines.cc
> +++ b/gcc/cp/coroutines.cc
> @@ -4605,6 +4605,7 @@ cp_coroutine_transform::build_ramp_function ()
>   
>     tree promise_type = get_coroutine_promise_type (orig_fn_decl);
>     tree fn_return_type = TREE_TYPE (TREE_TYPE (orig_fn_decl));
> +  bool void_ramp_p = VOID_TYPE_P (fn_return_type);
>   
>     /* [dcl.fct.def.coroutine] / 10 (part1)
>       The unqualified-id get_return_object_on_allocation_failure is looked up
> @@ -4771,7 +4772,7 @@ cp_coroutine_transform::build_ramp_function ()
>         tree cond = build1 (CONVERT_EXPR, frame_ptr_type, nullptr_node);
>         cond = build2 (EQ_EXPR, boolean_type_node, coro_fp, cond);
>         finish_if_stmt_cond (cond, if_stmt);
> -      if (VOID_TYPE_P (fn_return_type))
> +      if (void_ramp_p)
>   	{
>   	  /* Execute the get-return-object-on-alloc-fail call...  */
>   	  finish_expr_stmt (grooaf);
> @@ -4971,17 +4972,27 @@ cp_coroutine_transform::build_ramp_function ()
>   				     coro_get_return_object_identifier,
>   				     fn_start, NULL, /*musthave=*/true);
>     /* Without a return object we haven't got much clue what's going on.  */
> -  if (get_ro == error_mark_node)
> +  if (!get_ro || get_ro == error_mark_node)
>       {
>         BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_outer_bind);
>         /* Suppress warnings about the missing return value.  */
>         suppress_warning (orig_fn_decl, OPT_Wreturn_type);
>         return false;
>       }
> +
> +  /* Check for a bad get return object type.
> +     [dcl.fct.def.coroutine] / 7 requires:
> +     The expression promise.get_return_object() is used to initialize the
> +     returned reference or prvalue result object ... */
> +  tree gro_type = TREE_TYPE (get_ro);
> +  if (VOID_TYPE_P (gro_type) && !void_ramp_p)
> +    {
> +      error_at (fn_start, "no viable conversion from %<void%> provided by"
> +		" %<get_return_object%> to return type %qT", fn_return_type);
> +      return false;
> +    }
>   
>     tree gro_context_body = push_stmt_list ();
> -  tree gro_type = TREE_TYPE (get_ro);
> -  bool gro_is_void_p = VOID_TYPE_P (gro_type);
>   
>     tree gro = NULL_TREE;
>     tree gro_bind_vars = NULL_TREE;
> @@ -4990,8 +5001,11 @@ cp_coroutine_transform::build_ramp_function ()
>     tree gro_cleanup_stmt = NULL_TREE;
>     /* We have to sequence the call to get_return_object before initial
>        suspend.  */
> -  if (gro_is_void_p)
> -    r = get_ro;
> +  if (void_ramp_p)
> +    {
> +      gcc_checking_assert (VOID_TYPE_P (gro_type));
> +      r = get_ro;
> +    }
>     else if (same_type_p (gro_type, fn_return_type))
>       {
>        /* [dcl.fct.def.coroutine] / 7
> @@ -5072,28 +5086,11 @@ cp_coroutine_transform::build_ramp_function ()
>        for an object of the return type.  */
>   
>     if (same_type_p (gro_type, fn_return_type))
> -    r = gro_is_void_p ? NULL_TREE : DECL_RESULT (orig_fn_decl);
> -  else if (!gro_is_void_p)
> +    r = void_ramp_p ? NULL_TREE : DECL_RESULT (orig_fn_decl);
> +  else
>       /* check_return_expr will automatically return gro as an rvalue via
>          treat_lvalue_as_rvalue_p.  */
>       r = gro;
> -  else if (CLASS_TYPE_P (fn_return_type))
> -    {
> -      /* For class type return objects, we can attempt to construct,
> -	 even if the gro is void. ??? Citation ??? c++/100476  */
> -      r = build_special_member_call (NULL_TREE,
> -				     complete_ctor_identifier, NULL,
> -				     fn_return_type, LOOKUP_NORMAL,
> -				     tf_warning_or_error);
> -      r = build_cplus_new (fn_return_type, r, tf_warning_or_error);
> -    }
> -  else
> -    {
> -      /* We can't initialize a non-class return value from void.  */
> -      error_at (fn_start, "cannot initialize a return object of type"
> -		" %qT with an rvalue of type %<void%>", fn_return_type);
> -      return false;
> -    }
>   
>     /* Without a relevant location, bad conversions in finish_return_stmt result
>        in unusable diagnostics, since there is not even a mention of the
> diff --git a/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C b/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
> index 85aadad93b2..a7e3f3d1ac7 100644
> --- a/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
> +++ b/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
> @@ -27,7 +27,7 @@ struct std::coroutine_traits<R, HandleRef, T...> {
>   };
>   
>   int
> -my_coro (std::coroutine_handle<>& h) // { dg-error {cannot initialize a return object of type 'int' with an rvalue of type 'void'} }
> +my_coro (std::coroutine_handle<>& h) // { dg-error {no viable conversion from 'void' provided by 'get_return_object' to return type 'int'} }
>   {
>     PRINT ("coro1: about to return");
>     co_return;
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr102489.C b/gcc/testsuite/g++.dg/coroutines/pr102489.C
> index 0ef06daa211..15b85f4375b 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr102489.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr102489.C
> @@ -9,7 +9,7 @@ struct footask {
>       std::suspend_never initial_suspend();
>       std::suspend_never final_suspend() noexcept;
>       void unhandled_exception();
> -    void get_return_object();
> +    footask get_return_object();
>     };
>     std::suspend_always foo;
>     footask taskfun() { co_await foo; }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr103868.C b/gcc/testsuite/g++.dg/coroutines/pr103868.C
> index fd05769db3d..0ce40b699ce 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr103868.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr103868.C
> @@ -116,7 +116,7 @@ struct awaitable_frame_base {
>     template <typename T> auto await_transform(T a) { return a; }
>   };
>   template <> struct awaitable_frame<void> : awaitable_frame_base {
> -  void get_return_object();
> +  awaitable<void> get_return_object();
>   };
>   } // namespace detail
>   } // namespace asio
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> index 6e091526fe7..2d886fd387d 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> @@ -39,9 +39,10 @@ public:
>     std::g initial_suspend();
>     l final_suspend() noexcept;
>   };
> +class n;
>   class m : public j {
>   public:
> -  void get_return_object();
> +  n get_return_object();
>     void unhandled_exception();
>   };
>   class n {
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> index 98c5a7e3eee..f12897e8690 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> @@ -16,25 +16,7 @@ struct b {
>     void await_resume();
>   };
>   } // namespace std
> -
> -template <typename d> auto ab(int ac, d ad) -> decltype(ad.e(ac));
> -int f;
> -class h {
> -  class j {
> -  public:
> -    bool await_ready() noexcept;
> -    void await_suspend(std::coroutine_handle<>) noexcept;
> -    void await_resume() noexcept;
> -  };
> -
> -public:
> -  void get_return_object();
> -  std::b initial_suspend();
> -  j final_suspend() noexcept;
> -  void unhandled_exception();
> -  template <typename g>
> -    auto await_transform (g c) { return ab(f, c); }
> -};
> +class h;
>   template <typename, typename = int> class k {
>   public:
>     using promise_type = h;
> @@ -64,6 +46,25 @@ my_coro (k<aj, ak> am, ai) {
>       ;
>   }
>   
> +template <typename d> auto ab(int ac, d ad) -> decltype(ad.e(ac));
> +int f;
> +class h {
> +  class j {
> +  public:
> +    bool await_ready() noexcept;
> +    void await_suspend(std::coroutine_handle<>) noexcept;
> +    void await_resume() noexcept;
> +  };
> +
> +public:
> +  k<int> get_return_object();
> +  std::b initial_suspend();
> +  j final_suspend() noexcept;
> +  void unhandled_exception();
> +  template <typename g>
> +    auto await_transform (g c) { return ab(f, c); }
> +};
> +
>   void foo () {
>     k<int> a;
>     my_coro (a, [] {});
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr96749-2.C b/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
> index 43052b57dd9..3d764527291 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
> @@ -27,7 +27,7 @@ struct Task {
>   	struct promise_type {
>   		auto initial_suspend() { return std::suspend_always{}; }
>   		auto final_suspend() noexcept { return std::suspend_always{}; }
> -		void get_return_object() {}
> +		Task get_return_object() ;
>   		void unhandled_exception() {}
>   	};
>   };
diff mbox series

Patch

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 2f128db42e1..3c0757403ce 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -4605,6 +4605,7 @@  cp_coroutine_transform::build_ramp_function ()
 
   tree promise_type = get_coroutine_promise_type (orig_fn_decl);
   tree fn_return_type = TREE_TYPE (TREE_TYPE (orig_fn_decl));
+  bool void_ramp_p = VOID_TYPE_P (fn_return_type);
 
   /* [dcl.fct.def.coroutine] / 10 (part1)
     The unqualified-id get_return_object_on_allocation_failure is looked up
@@ -4771,7 +4772,7 @@  cp_coroutine_transform::build_ramp_function ()
       tree cond = build1 (CONVERT_EXPR, frame_ptr_type, nullptr_node);
       cond = build2 (EQ_EXPR, boolean_type_node, coro_fp, cond);
       finish_if_stmt_cond (cond, if_stmt);
-      if (VOID_TYPE_P (fn_return_type))
+      if (void_ramp_p)
 	{
 	  /* Execute the get-return-object-on-alloc-fail call...  */
 	  finish_expr_stmt (grooaf);
@@ -4971,17 +4972,27 @@  cp_coroutine_transform::build_ramp_function ()
 				     coro_get_return_object_identifier,
 				     fn_start, NULL, /*musthave=*/true);
   /* Without a return object we haven't got much clue what's going on.  */
-  if (get_ro == error_mark_node)
+  if (!get_ro || get_ro == error_mark_node)
     {
       BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_outer_bind);
       /* Suppress warnings about the missing return value.  */
       suppress_warning (orig_fn_decl, OPT_Wreturn_type);
       return false;
     }
+ 
+  /* Check for a bad get return object type.
+     [dcl.fct.def.coroutine] / 7 requires:
+     The expression promise.get_return_object() is used to initialize the
+     returned reference or prvalue result object ... */
+  tree gro_type = TREE_TYPE (get_ro);
+  if (VOID_TYPE_P (gro_type) && !void_ramp_p)
+    {
+      error_at (fn_start, "no viable conversion from %<void%> provided by"
+		" %<get_return_object%> to return type %qT", fn_return_type);
+      return false;
+    }
 
   tree gro_context_body = push_stmt_list ();
-  tree gro_type = TREE_TYPE (get_ro);
-  bool gro_is_void_p = VOID_TYPE_P (gro_type);
 
   tree gro = NULL_TREE;
   tree gro_bind_vars = NULL_TREE;
@@ -4990,8 +5001,11 @@  cp_coroutine_transform::build_ramp_function ()
   tree gro_cleanup_stmt = NULL_TREE;
   /* We have to sequence the call to get_return_object before initial
      suspend.  */
-  if (gro_is_void_p)
-    r = get_ro;
+  if (void_ramp_p)
+    {
+      gcc_checking_assert (VOID_TYPE_P (gro_type));
+      r = get_ro;
+    }
   else if (same_type_p (gro_type, fn_return_type))
     {
      /* [dcl.fct.def.coroutine] / 7
@@ -5072,28 +5086,11 @@  cp_coroutine_transform::build_ramp_function ()
      for an object of the return type.  */
 
   if (same_type_p (gro_type, fn_return_type))
-    r = gro_is_void_p ? NULL_TREE : DECL_RESULT (orig_fn_decl);
-  else if (!gro_is_void_p)
+    r = void_ramp_p ? NULL_TREE : DECL_RESULT (orig_fn_decl);
+  else
     /* check_return_expr will automatically return gro as an rvalue via
        treat_lvalue_as_rvalue_p.  */
     r = gro;
-  else if (CLASS_TYPE_P (fn_return_type))
-    {
-      /* For class type return objects, we can attempt to construct,
-	 even if the gro is void. ??? Citation ??? c++/100476  */
-      r = build_special_member_call (NULL_TREE,
-				     complete_ctor_identifier, NULL,
-				     fn_return_type, LOOKUP_NORMAL,
-				     tf_warning_or_error);
-      r = build_cplus_new (fn_return_type, r, tf_warning_or_error);
-    }
-  else
-    {
-      /* We can't initialize a non-class return value from void.  */
-      error_at (fn_start, "cannot initialize a return object of type"
-		" %qT with an rvalue of type %<void%>", fn_return_type);
-      return false;
-    }
 
   /* Without a relevant location, bad conversions in finish_return_stmt result
      in unusable diagnostics, since there is not even a mention of the
diff --git a/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C b/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
index 85aadad93b2..a7e3f3d1ac7 100644
--- a/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
+++ b/gcc/testsuite/g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C
@@ -27,7 +27,7 @@  struct std::coroutine_traits<R, HandleRef, T...> {
 };
 
 int
-my_coro (std::coroutine_handle<>& h) // { dg-error {cannot initialize a return object of type 'int' with an rvalue of type 'void'} }
+my_coro (std::coroutine_handle<>& h) // { dg-error {no viable conversion from 'void' provided by 'get_return_object' to return type 'int'} }
 {
   PRINT ("coro1: about to return");
   co_return;
diff --git a/gcc/testsuite/g++.dg/coroutines/pr102489.C b/gcc/testsuite/g++.dg/coroutines/pr102489.C
index 0ef06daa211..15b85f4375b 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr102489.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr102489.C
@@ -9,7 +9,7 @@  struct footask {
     std::suspend_never initial_suspend();
     std::suspend_never final_suspend() noexcept;
     void unhandled_exception();
-    void get_return_object();
+    footask get_return_object();
   };
   std::suspend_always foo;
   footask taskfun() { co_await foo; }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr103868.C b/gcc/testsuite/g++.dg/coroutines/pr103868.C
index fd05769db3d..0ce40b699ce 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr103868.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr103868.C
@@ -116,7 +116,7 @@  struct awaitable_frame_base {
   template <typename T> auto await_transform(T a) { return a; }
 };
 template <> struct awaitable_frame<void> : awaitable_frame_base {
-  void get_return_object();
+  awaitable<void> get_return_object();
 };
 } // namespace detail
 } // namespace asio
diff --git a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
index 6e091526fe7..2d886fd387d 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
@@ -39,9 +39,10 @@  public:
   std::g initial_suspend();
   l final_suspend() noexcept;
 };
+class n;
 class m : public j {
 public:
-  void get_return_object();
+  n get_return_object();
   void unhandled_exception();
 };
 class n {
diff --git a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
index 98c5a7e3eee..f12897e8690 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
@@ -16,25 +16,7 @@  struct b {
   void await_resume();
 };
 } // namespace std
-
-template <typename d> auto ab(int ac, d ad) -> decltype(ad.e(ac));
-int f;
-class h {
-  class j {
-  public:
-    bool await_ready() noexcept;
-    void await_suspend(std::coroutine_handle<>) noexcept;
-    void await_resume() noexcept;
-  };
-
-public:
-  void get_return_object();
-  std::b initial_suspend();
-  j final_suspend() noexcept;
-  void unhandled_exception();
-  template <typename g> 
-    auto await_transform (g c) { return ab(f, c); }
-};
+class h;
 template <typename, typename = int> class k {
 public:
   using promise_type = h;
@@ -64,6 +46,25 @@  my_coro (k<aj, ak> am, ai) {
     ;
 }
 
+template <typename d> auto ab(int ac, d ad) -> decltype(ad.e(ac));
+int f;
+class h {
+  class j {
+  public:
+    bool await_ready() noexcept;
+    void await_suspend(std::coroutine_handle<>) noexcept;
+    void await_resume() noexcept;
+  };
+
+public:
+  k<int> get_return_object();
+  std::b initial_suspend();
+  j final_suspend() noexcept;
+  void unhandled_exception();
+  template <typename g> 
+    auto await_transform (g c) { return ab(f, c); }
+};
+
 void foo () {
   k<int> a;
   my_coro (a, [] {});
diff --git a/gcc/testsuite/g++.dg/coroutines/pr96749-2.C b/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
index 43052b57dd9..3d764527291 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr96749-2.C
@@ -27,7 +27,7 @@  struct Task {
 	struct promise_type {
 		auto initial_suspend() { return std::suspend_always{}; }
 		auto final_suspend() noexcept { return std::suspend_always{}; }
-		void get_return_object() {}
+		Task get_return_object() ;
 		void unhandled_exception() {}
 	};
 };