diff --git a/src/cp_gen_be.c b/src/cp_gen_be.c index fdc3911326..651d96bd84 100644 --- a/src/cp_gen_be.c +++ b/src/cp_gen_be.c @@ -19959,6 +19959,7 @@ gen_expr that might end up generating this expr as a temporary. #endif /* BUILTIN_FUNCTIONS_ENABLED */ case enk_yield: /* yield expression: generate "co_yield ". */ + if (need_parens) write_tok_ch('('); write_tok_str("co_yield "); expr = expr->variant.await_info.operand; /* Skip nodes implementing the binding of the operand to the call. */ @@ -19990,12 +19991,15 @@ gen_expr that might end up generating this expr as a temporary. statement). */ expr = expr->next->next; gen_expression(expr); + if (need_parens) write_tok_ch(')'); break; case enk_await: + if (need_parens) write_tok_ch('('); write_tok_str((msvc_is_generated_code_target && msvc_target_version_number < 1900) ? "__await " : "co_await "); gen_expression(expr->variant.await_info.operand); + if (need_parens) write_tok_ch(')'); break; case enk_fold: gen_fold_expression(expr); diff --git a/src/expr.c b/src/expr.c index eeb5fed013..19f077ddbc 100644 --- a/src/expr.c +++ b/src/expr.c @@ -49263,7 +49263,24 @@ suspend point. } /* if */ node->variant.await_info.ready_resume_suspend->next->next = make_node_from_operand(&suspend_call); - make_expression_operand(node, operand); + if (is_a_glvalue(&resume_call)) { + /* await_resume returns a reference, so the co_await expression is a + glvalue too (an lvalue or xvalue, like the await_resume call). As + for a call, give the node the reference type and put a reference + indirection on top of it. */ + an_expr_node_ptr call = strip_ref_indirect( + node->variant.await_info.ready_resume_suspend->next, + /*parens_also=*/FALSE); + if (is_any_reference_type(call->type)) { + node->type = call->type; + node = add_ref_indirection_to_node(node); + if (is_an_xvalue(&resume_call)) { + node->is_lvalue = FALSE; + node->is_xvalue = TRUE; + } /* if */ + } /* if */ + } /* if */ + make_lvalue_or_rvalue_expression_operand(node, operand); done:; } /* add_await_to_operand */ diff --git a/src/pending_changes/gh-447-co-await-value-category.txt b/src/pending_changes/gh-447-co-await-value-category.txt new file mode 100644 index 0000000000..3407edc810 --- /dev/null +++ b/src/pending_changes/gh-447-co-await-value-category.txt @@ -0,0 +1,19 @@ +[GH #447] +Value category of co_await and co_yield expressions + +A co_await or co_yield expression was always treated as a prvalue, even +when the await_resume call returns a reference. It now has the value +category of the await_resume call, so code like this is accepted: + + struct bool_ref_awaiter { + bool await_ready() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; + bool &await_resume() noexcept; + }; + task f() { + (co_await bool_ref_awaiter{}) = false; + } + +Also, the C++-generating back end now puts parentheses around a co_await +or co_yield expression that is an operand of another operator, so that +"(co_await x).m" is no longer generated as "co_await x.m". diff --git a/tests/tests/changes/.GH447.rto/default.1.1.txt b/tests/tests/changes/.GH447.rto/default.1.1.txt new file mode 100644 index 0000000000..4163d8bb9a --- /dev/null +++ b/tests/tests/changes/.GH447.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++23 Test_name.c diff --git a/tests/tests/changes/GH447.sft.cpp b/tests/tests/changes/GH447.sft.cpp new file mode 100644 index 0000000000..fac9643558 --- /dev/null +++ b/tests/tests/changes/GH447.sft.cpp @@ -0,0 +1,71 @@ +//type:fp +//options:--c++23 +//remark:[GH #447] co_await has the value category of await_resume() +// +// A co_await or co_yield expression was always treated as a prvalue, so +// assigning to the result of an await_resume() that returns an lvalue +// reference was rejected. The generated C++ also needs parentheses around +// co_await and co_yield when they are operands (e.g., of "." or "="). + +namespace std { + template struct coroutine_handle; + template <> struct coroutine_handle<> { void *address(); }; + template struct coroutine_handle { + static coroutine_handle from_address(void *); + operator coroutine_handle<>() noexcept; + }; + template struct coroutine_traits { + using promise_type = typename R::promise_type; + }; + template struct is_same { static constexpr bool value = false; }; + template struct is_same { static constexpr bool value = true; }; +} // namespace std + +struct suspend { + bool await_ready() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template struct awaiter { + bool await_ready() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; + R await_resume() noexcept; +}; + +struct task; + +struct promise { + suspend initial_suspend() noexcept; + suspend final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + task get_return_object(); + awaiter yield_value(int); +}; + +struct task { + using promise_type = promise; +}; + +struct S { int m; int get(); }; + +task g() { + int i = (co_await awaiter{}).m; + int j = (co_await awaiter{}).get(); + int k = (co_await awaiter{}).m = 1; +} + +task f() { + (co_await awaiter{}) = false; + bool *p = &co_await awaiter{}; + decltype(auto) a = co_await awaiter{}; + decltype(auto) b = co_await awaiter{}; + decltype(auto) c = co_await awaiter{}; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + (co_yield 1) = 2; + decltype(auto) d = co_yield 1; + static_assert(std::is_same::value); +}