On 7/31/24 6:54 AM, Arsen Arsenović wrote:
Tested on x86_64-pc-linux-gnu.  OK for trunk?

TIA, have a lovely day.
---------- >8 ----------
By doing so, we can get diagnostics in template decls when we know we
can.  For instance, in the following:

   awaitable g();
   template<typename>
   task f()
   {
     co_await g();
     co_yield 1;
     co_return "foo";
   }

... the coroutine promise type in each statement is always
std::coroutine_handle<task>::promise_type, and all of the operands are
not type-dependent, so we can always compute the resulting types (and
expected types) of these expressions and statements.

Also, when we do not know the type of the CO_AWAIT_EXPR or
CO_YIELD_EXPR, we now return NULL_TREE as the type rather than
unknown_type_node.  This is more correct, since the type is not unknown,
it just isn't determined yet.  This also means we can remove the
CO_AWAIT_EXPR and CO_YIELD_EXPR special-cases from
type_dependent_expression_p.

PR c++/112341 - error: insufficient contextual information to determine type on 
co_await result in function template

gcc/cp/ChangeLog:

        PR c++/112341
        * coroutines.cc (struct coroutine_info): Also cache the
        traits type.
        (ensure_coro_initialized): New function.  Makes sure we have
        initialized the coroutine state successfully, or informs the
        caller should it fail to do so.  Extracted from
        coro_promise_type_found_p.
        (coro_get_traits_class): New function.  Gets the (cached)
        coroutine traits type for a given coroutine.  Extracted from
        coro_promise_type_found_p and refactored to cache the result.
        (coro_promise_type_found_p): Use the two functions above.
        (build_template_co_await_expr): New function.  Builds a
        CO_AWAIT_EXPR representing a CO_AWAIT_EXPR in a template
        declaration.
        (build_co_await): Use the above if processing_template_decl, and
        give it a propert type.
        (defer_expansion_p): New function.  Returns true iff its
        argument is a type-dependent expression OR the current functions
        traits class is type dependent.
        (finish_co_await_expr): Defer expansion only in the case
        defer_expasnion_p returns true.
        (finish_co_yield_expr): Ditto.
        (finish_co_return_stmt): Ditto.
        * pt.cc (type_dependent_expression_p): Do not treat
        CO_AWAIT/CO_YIELD specially.

gcc/testsuite/ChangeLog:

        PR c++/112341
        * g++.dg/coroutines/pr112341-2.C: New test.
        * g++.dg/coroutines/pr112341-3.C: New test.
        * g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C: New
        test.
        * g++.dg/coroutines/pr112341.C: New test.
---
  gcc/cp/coroutines.cc                          | 142 ++++++++++++++----
  gcc/cp/pt.cc                                  |   5 -
  gcc/testsuite/g++.dg/coroutines/pr112341-2.C  |  25 +++
  gcc/testsuite/g++.dg/coroutines/pr112341-3.C  |  65 ++++++++
  gcc/testsuite/g++.dg/coroutines/pr112341.C    |  21 +++
  .../torture/co-yield-03-tmpl-nondependent.C   | 140 +++++++++++++++++
  6 files changed, 361 insertions(+), 37 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-2.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-3.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341.C
  create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 127a1c06b56e..9494cb499454 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -85,6 +85,7 @@ struct GTY((for_user)) coroutine_info
    tree actor_decl;    /* The synthesized actor function.  */
    tree destroy_decl;  /* The synthesized destroy function.  */
    tree promise_type;  /* The cached promise type for this function.  */
+  tree traits_type;   /* The cached traits type for this function.  */
    tree handle_type;   /* The cached coroutine handle for this function.  */
    tree self_h_proxy;  /* A handle instance that is used as the proxy for the
                         one that will eventually be allocated in the coroutine
@@ -429,11 +430,12 @@ find_promise_type (tree traits_class)
    return promise_type;
  }
+/* Perform initialization of the coroutine processor state, if not done
+   before.  */
+
  static bool
-coro_promise_type_found_p (tree fndecl, location_t loc)
+ensure_coro_initialized (location_t loc)
  {
-  gcc_assert (fndecl != NULL_TREE);
-
    if (!coro_initialized)
      {
        /* Trees we only need to create once.
@@ -466,6 +468,33 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
coro_initialized = true;
      }
+  return true;
+}
+
+/* Try to get the coroutine traits class.  */
+static tree
+coro_get_traits_class (tree fndecl, location_t loc)
+{
+  gcc_assert (fndecl != NULL_TREE);
+
+  if (!ensure_coro_initialized (loc))
+    /* We can't continue.  */
+    return error_mark_node;
+
+  coroutine_info *coro_info = get_or_insert_coroutine_info (fndecl);
+  auto& traits_type = coro_info->traits_type;
+  if (!traits_type)
+    traits_type = instantiate_coro_traits (fndecl, loc);
+  return traits_type;
+}
+
+static bool
+coro_promise_type_found_p (tree fndecl, location_t loc)
+{
+  gcc_assert (fndecl != NULL_TREE);
+
+  if (!ensure_coro_initialized (loc))
+    return false;
/* Save the coroutine data on the side to avoid the overhead on every
       function decl tree.  */
@@ -480,7 +509,7 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
        /* Get the coroutine traits template class instance for the function
         signature we have - coroutine_traits <R, ...>  */
- tree templ_class = instantiate_coro_traits (fndecl, loc);
+      tree templ_class = coro_get_traits_class (fndecl, loc);
/* Find the promise type for that. */
        coro_info->promise_type = find_promise_type (templ_class);
@@ -914,6 +943,19 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
    return coro_diagnose_throwing_fn (fn);
  }
+/* Build a co_await suitable for later expansion. */
+
+tree
+build_template_co_await_expr (location_t kw, tree expr, tree type, tree kind)

For consistency with other build*, let's put type before expr.

+{
+  tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, type, expr,
+                            NULL_TREE, NULL_TREE, NULL_TREE,
+                            kind);

Typically in templates we use build_min_nt_loc for the dependent case, and build_min_non_dep for the non-dependent case...though I suppose that distinction is primarily about handling TREE_SIDE_EFFECTS, which is less significant when we're always setting it to true anyway. So I guess this is fine.

+  TREE_SIDE_EFFECTS (aw_expr) = true;
+  return aw_expr;
+}
+
+
  /*  This performs [expr.await] bullet 3.3 and validates the interface 
obtained.
      It is also used to build the initial and final suspend points.
@@ -922,11 +964,24 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
      A, the original yield/await expr, is found at source location LOC.
We will be constructing a CO_AWAIT_EXPR for a suspend point of one of
-    the four suspend_point_kind kinds.  This is indicated by SUSPEND_KIND.  */
+    the four suspend_point_kind kinds.  This is indicated by SUSPEND_KIND.
+
+    In the case that we're processing a template declaration, we can't save
+    actual awaiter expressions as the frame type will differ between
+    instantiations, but we can generate them to type-check them and compute the
+    resulting expression type.  In those cases, we will return a
+    template-appropriate CO_AWAIT_EXPR and throw away the rest of the results.
+    Such an expression requires the original co_await operand unaltered.  Pass
+    it as ORIG_OPERAND.  If it is the same as 'a', you can pass error_mark_node
+    (the default) to use 'a' as the value.  */

Why error_mark_node rather than NULL_TREE as the default? I'd prefer to reserve error_mark_node for actual errors.

  static tree
-build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
+build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind,
+               tree orig_operand = error_mark_node)
  {
+  if (orig_operand == error_mark_node)
+    orig_operand = a;
+
    /* Try and overload of operator co_await, .... */
    tree o;
    if (MAYBE_CLASS_TYPE_P (TREE_TYPE (a)))
@@ -1131,11 +1186,13 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind)
    if (REFERENCE_REF_P (e_proxy))
      e_proxy = TREE_OPERAND (e_proxy, 0);
+ tree awrs_type = TREE_TYPE (TREE_TYPE (awrs_func));
+  tree suspend_kind_cst = build_int_cst (integer_type_node,
+                                        (int) suspend_kind);
    tree await_expr = build5_loc (loc, CO_AWAIT_EXPR,
-                               TREE_TYPE (TREE_TYPE (awrs_func)),
+                               awrs_type,
                                a, e_proxy, o, awaiter_calls,
-                               build_int_cst (integer_type_node,
-                                              (int) suspend_kind));
+                               suspend_kind_cst);
    TREE_SIDE_EFFECTS (await_expr) = true;
    if (te)
      {
@@ -1144,7 +1201,26 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind)
        await_expr = te;
      }
    SET_EXPR_LOCATION (await_expr, loc);
-  return convert_from_reference (await_expr);
+
+  if (processing_template_decl)
+    /* FIXME: Can we reuse the results above rather than re-make at template
+       instantiating?  The "obvious" solution of checking if we have a fully
+       built expression and recurring into the operands and 'e' call
+       expressions seems to cause PR103868 again.  */

In general all that we keep is types and name lookup results, everything else gets rebuilt at instantiation time, I wouldn't try to do anything different for co_await.

+    return build_template_co_await_expr (loc, orig_operand, awrs_type,
+                                        suspend_kind_cst);
+ return convert_from_reference (await_expr);
+}
+
+/* Returns true iff EXPR or the current functions traits class are dependent.
+   In those cases, we can't decide what the type of our expressions is, so we
+   defer expansion entirely.  */
+
+static bool
+defer_expansion_p (tree expr, location_t loc)

This name doesn't suggest it's specific to coroutines. Maybe coro_dependent_p?

I'm also skeptical about passing in loc here; we shouldn't need to give a diagnostic by this point.

Relatedly, it looks like none of the callers of coro_get_traits_class deal with error_mark_node return. If you aren't going to handle that, maybe coro_get_traits_class should abort instead of returning it. Or call ensure_coro_initialized somewhere else.

Jason

Reply via email to