]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++, coroutines: Avoid expanding within templates [PR103868]
authorNathan Sidwell <nathan@acm.org>
Sun, 3 Apr 2022 10:35:03 +0000 (11:35 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Thu, 28 Apr 2022 12:50:59 +0000 (13:50 +0100)
This is a forward-port of a patch by Nathan (against 10.x) which fixes an open
PR.

We are ICEing because we ended up tsubst_copying something that had already
been tsubst, leading to an assert failure (mostly such repeated tsubsting is
harmless).

We had a non-dependent co_await in a non-dependent-type template fn, so we
processed it at definition time, and then reprocessed at instantiation time.
We fix this here by deferring substitution while processing templates.

Additional observations (for a better future fix, in the GCC13 timescale):

Exprs only have dependent type if at least one operand is dependent which was
what the current code was intending to do.  Coroutines have the additional
wrinkle, that the current fn's type is an implicit operand.

So, if the coroutine function's type is not dependent, and the operand is not
dependent, we should determine the type of the co_await expression using the
DEPENDENT_EXPR wrapper machinery.  That allows us to determine the
subexpression type, but leave its operand unchanged and then instantiate it
later.

PR c++/103868

gcc/cp/ChangeLog:

* coroutines.cc (finish_co_await_expr): Do not process non-dependent
coroutine expressions at template definition time.
(finish_co_yield_expr): Likewise.
(finish_co_return_stmt): Likewise.

gcc/testsuite/ChangeLog:

* g++.dg/coroutines/pr103868.C: New test.

Co-Authored-by: Iain Sandoe <iain@sandoe.co.uk>
gcc/cp/coroutines.cc
gcc/testsuite/g++.dg/coroutines/pr103868.C [new file with mode: 0644]

index cdf6503c4d30df86c74adf89d787c938471ae231..64f4b4412e55d13b6aeaae652d0d4ea54c69ca55 100644 (file)
@@ -1148,10 +1148,13 @@ finish_co_await_expr (location_t kw, tree expr)
      extraneous warnings during substitution.  */
   suppress_warning (current_function_decl, OPT_Wreturn_type);
 
-  /* If we don't know the promise type, we can't proceed, build the
-     co_await with the expression unchanged.  */
-  tree functype = TREE_TYPE (current_function_decl);
-  if (dependent_type_p (functype) || type_dependent_expression_p (expr))
+  /* Defer expansion when we are processing a template.
+     FIXME: If the coroutine function's type is not dependent, and the operand
+     is not dependent, we should determine the type of the co_await expression
+     using the DEPENDENT_EXPR wrapper machinery.  That allows us to determine
+     the subexpression type, but leave its operand unchanged and then
+     instantiate it later.  */
+  if (processing_template_decl)
     {
       tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, unknown_type_node, expr,
                                 NULL_TREE, NULL_TREE, NULL_TREE,
@@ -1222,10 +1225,9 @@ finish_co_yield_expr (location_t kw, tree expr)
      extraneous warnings during substitution.  */
   suppress_warning (current_function_decl, OPT_Wreturn_type);
 
-  /* If we don't know the promise type, we can't proceed, build the
-     co_await with the expression unchanged.  */
-  tree functype = TREE_TYPE (current_function_decl);
-  if (dependent_type_p (functype) || type_dependent_expression_p (expr))
+  /* Defer expansion when we are processing a template; see FIXME in the
+     co_await code.  */
+  if (processing_template_decl)
     return build2_loc (kw, CO_YIELD_EXPR, unknown_type_node, expr, NULL_TREE);
 
   if (!coro_promise_type_found_p (current_function_decl, kw))
@@ -1307,10 +1309,9 @@ finish_co_return_stmt (location_t kw, tree expr)
       && check_for_bare_parameter_packs (expr))
     return error_mark_node;
 
-  /* If we don't know the promise type, we can't proceed, build the
-     co_return with the expression unchanged.  */
-  tree functype = TREE_TYPE (current_function_decl);
-  if (dependent_type_p (functype) || type_dependent_expression_p (expr))
+  /* Defer expansion when we are processing a template; see FIXME in the
+     co_await code.  */
+  if (processing_template_decl)
     {
       /* co_return expressions are always void type, regardless of the
         expression type.  */
diff --git a/gcc/testsuite/g++.dg/coroutines/pr103868.C b/gcc/testsuite/g++.dg/coroutines/pr103868.C
new file mode 100644 (file)
index 0000000..8687eff
--- /dev/null
@@ -0,0 +1,150 @@
+// { dg-additional-options "-fpreprocessed -std=gnu++20 -w -fconcepts" }
+int _M_invoke___functor;
+namespace std {
+template <int __v> struct integral_constant {
+  static constexpr bool value = __v;
+};
+template <typename> struct decay { typedef int type; };
+template <bool, typename> struct enable_if;
+template <typename _Tp> struct enable_if<true, _Tp> { typedef _Tp type; };
+template <typename, typename _Fn, typename... _Args>
+void __invoke_impl(_Fn __f, _Args... __args) {
+  __f(__args...);
+}
+template <typename, typename _Callable, typename... _Args>
+void __invoke_r(_Callable __fn, _Args... __args) {
+  using __result = int;
+  using __type = __result;
+  __invoke_impl<__type>(__fn, __args...);
+}
+template <typename> class function;
+template <typename _Functor> struct _Base_manager {
+  static _Functor *_M_get_pointer(int) {}
+};
+template <typename, typename> class _Function_handler;
+template <typename _Res, typename _Functor, typename... _ArgTypes>
+struct _Function_handler<_Res(_ArgTypes...), _Functor> {
+  static _Res _M_invoke(_ArgTypes... __args) {
+    auto __trans_tmp_1 =
+        _Base_manager<_Functor>::_M_get_pointer(_M_invoke___functor);
+    __invoke_r<_Res>(*__trans_tmp_1, __args...);
+  }
+};
+template <typename _Res, typename... _ArgTypes>
+struct function<_Res(_ArgTypes...)> {
+  template <typename _Functor> function(_Functor) {
+    _Function_handler<_Res(_ArgTypes...), _Functor>::_M_invoke;
+  }
+};
+template <typename _Tp> struct __shared_ptr_access {
+  using element_type = _Tp;
+  element_type *operator->();
+};
+template <typename> void make_shared();
+} // namespace std
+namespace boost {
+using std::decay;
+using std::enable_if;
+using std::integral_constant;
+namespace asio {
+namespace detail {
+template <typename> struct is_completion_signature;
+template <typename R, typename... Args>
+struct is_completion_signature<R(Args...)> : integral_constant<true> {};
+} // namespace detail
+template <typename T>
+concept completion_signature = detail::is_completion_signature<T>::value;
+template <typename...> struct async_result;
+namespace detail {
+template <completion_signature>
+struct async_result_has_initiate_memfn : integral_constant<1> {};
+} // namespace detail
+template <typename CompletionToken, completion_signature... Signatures,
+          typename Initiation>
+enable_if<detail::async_result_has_initiate_memfn<Signatures...>::value,
+          decltype(async_result<typename decay<CompletionToken>::type,
+                                Signatures...>::initiate(0))>::type
+async_initiate(Initiation) {}
+} // namespace asio
+} // namespace boost
+namespace malloy::websocket {
+struct connection {
+  auto read(auto, auto done) {
+    auto wrapper = [] {};
+    return boost::asio::async_initiate<decltype(done), void()>(wrapper);
+  }
+};
+} // namespace malloy::websocket
+namespace std {
+template <typename...> struct coroutine_traits;
+template <typename = void> struct coroutine_handle {
+  operator coroutine_handle<>();
+};
+struct suspend_always {
+  bool await_ready();
+  void await_suspend(coroutine_handle<>);
+  void await_resume();
+};
+} // namespace std
+namespace boost {
+namespace asio {
+namespace detail {
+using std::coroutine_handle;
+using std::suspend_always;
+template <typename> class awaitable_frame;
+} // namespace detail
+template <typename, typename = int> struct awaitable {
+  bool await_ready();
+  template <class U>
+  void await_suspend(detail::coroutine_handle<detail::awaitable_frame<U>>);
+  void await_resume();
+};
+namespace detail {
+struct awaitable_frame_base {
+  auto initial_suspend() { return suspend_always(); }
+  auto final_suspend() noexcept {
+    struct result {
+      bool await_ready() noexcept;
+      void await_suspend(coroutine_handle<>) noexcept;
+      void await_resume() noexcept;
+    };
+    return result{};
+  }
+  void unhandled_exception();
+  template <typename T> auto await_transform(T a) { return a; }
+};
+template <> struct awaitable_frame<void> : awaitable_frame_base {
+  void get_return_object();
+};
+} // namespace detail
+} // namespace asio
+} // namespace boost
+namespace std {
+template <typename T, typename Executor, typename... Args>
+struct coroutine_traits<boost::asio::awaitable<T, Executor>, Args...> {
+  typedef boost::asio::detail::awaitable_frame<T> promise_type;
+};
+} // namespace std
+namespace boost {
+namespace asio {
+struct use_awaitable_t {
+  use_awaitable_t(char, int, char);
+} use_awaitable(0, 0, 0);
+template <typename Executor, typename R, typename... Args>
+struct async_result<Executor, R(Args...)> {
+  template <typename Initiation> static awaitable<int> initiate(Initiation);
+};
+} // namespace asio
+} // namespace boost
+namespace malloy::test {
+void roundtrip_coro(std::function<void(int)>);
+}
+using boost::asio::awaitable;
+template <int> awaitable<void> client_ws_handler_coro() {
+  std::__shared_ptr_access<malloy::websocket::connection> conn;
+  auto buffer = std::make_shared<int>;
+  co_await conn->read(buffer, boost::asio::use_awaitable);
+}
+void port() {
+  malloy::test::roundtrip_coro([](auto) { client_ws_handler_coro<false>; });
+}