On 2/22/21 3:59 PM, Iain Sandoe wrote:
Hi,
PR96251 (and dups) is a rejects-valid bug against coroutines (but I’m
not sure that the problem is really there).
* coroutines cannot be constexpr.
* Part of the way that the coroutine implementation works is a
consequence of the "lazy discovery of coroutine-ness”; whenever
we first encounter a coroutine keyword...
.. we mark the function as a coroutine, and then we can deal with
diagnostics etc. that change under these circumstances. This
marking of the function is independent of whether keyword
expressions are type-dependent or not.
* when we encounter a coroutine keyword in a constexpr context
we error.
* the constexpr machinery also has been taught that coroutine
expressions should cause constexpr-ness to be rejected when
checking for "potentially constexpr".
So why is there a problem?
* lambdas are implicitly constexpr from C++17.
* the constexpr machinery tends to bail early *with a conservative
assumption that the expression is potentially constexpr* when it
finds a type-dependent expression - without evaluating sub-
expressions to see if they are valid (thus missing coroutine exprs.
in such positions).
The combination of these ^^ two things, means that for many generic
lambdas with non-trivial bodies - we then enter instantiation with a
constexpr type-dependent coroutine (which is a Thing that Should not
Be). As soon as we try to tsub any coroutine keyword expression
encountered, we error out.
* I was not able to see any way in which the instantiation process
could be made to bail in this case and re-try for non-constexpr.
Many of the other places that set cp_function_chain->invalid_constexpr
condition their errors on !is_instantiation_of_constexpr, which should
also fix this testcase.
* Nor able to see somewhere else where that decision could be made.
^^ these two points reflect that I'm not familiar with the constexpr
machinery.
* Proposed solution.
Since coroutines cannot be constexpr (not even sure what that would
mean in any practicable implementation). The fix proposed is to
reject constexpr-ness for coroutine functions as early as possible.
* a) We can prevent a generic lambda coroutine from being converted
to constexpr in finish_function (). Likewise, via the tests below,
we can
avoid it for regular lambdas.
b) We can also reject coroutine function decls early in both
is_valid_constexpr_fn () and potential_constant_expression_1 ().
So - is there some alternate solution that would be better?
====
(in some ways it seems pointless to delay rejection of a coroutine
function to some later point).
OTOH, I suppose that one could have some weird code where coroutine
coroutine keywords only appeared in a non-constexpr paths of the code
- but our current lowering is not prepared for such a circumstance.
AFAIU the standard, there's no dispensation from the "cannot be
constexpr" for such a code construction .. but ICBW.
In any event, the cases reported (and fixed by this patch) are not trying
anything so fiendishly clever.
Tested on x86_64-darwin.
Suggestions?
OK for master (after wider testing)?
thanks
Iain
= ----
coroutines cannot be constexpr, but generic lambdas (from C++17)
are implicitly assumed to be, and the processing of type-
dependent lambda bodies often terminates before any coroutine
expressions are encountered. For example, the PR notes cases
where the coro expressions are hidden by type-dependent for or
switch expressions.
The solution proposed is to deny coroutine generic lambdas. We also
tighten up the checks for is_valid_constexpr_fn() and do an early
test for coroutine functions in checking for potential constant
expressions.
gcc/cp/ChangeLog:
PR c++/96251
* constexpr.c (is_valid_constexpr_fn): Test for a
coroutine before the chekc for lambda.
(potential_constant_expression_1): Disallow coroutine
function decls.
* decl.c (finish_function): Do not mark generic
coroutine lambda templates as constexpr.
gcc/testsuite/ChangeLog:
PR c++/96251
* g++.dg/coroutines/pr96251.C: New test.
---
gcc/cp/constexpr.c | 7 ++++++-
gcc/cp/decl.c | 2 +-
gcc/testsuite/g++.dg/coroutines/pr96251.C | 22 ++++++++++++++++++++++
3 files changed, 29 insertions(+), 2 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/coroutines/pr96251.C
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 377fe322ee8..036bf04efa9 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -210,7 +210,9 @@ is_valid_constexpr_fn (tree fun, bool complain)
}
}
- if (LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun)) && cxx_dialect < cxx17)
+ if (DECL_COROUTINE_P (fun))
+ ret = false;
+ else if (LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun)) && cxx_dialect < cxx17)
{
ret = false;
if (complain)
@@ -7827,6 +7829,9 @@ potential_constant_expression_1 (tree t, bool
want_rval, bool strict, bool now,
switch (TREE_CODE (t))
{
case FUNCTION_DECL:
+ if (DECL_COROUTINE_P (t))
+ return false;
+ /* FALLTHROUGH. */
case BASELINK:
case TEMPLATE_DECL:
case OVERLOAD:
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 7fa8f52d667..3a089b5bee5 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -17374,7 +17374,7 @@ finish_function (bool inline_p)
if (cxx_dialect >= cxx17
&& LAMBDA_TYPE_P (CP_DECL_CONTEXT (fndecl)))
DECL_DECLARED_CONSTEXPR_P (fndecl)
- = ((processing_template_decl
+ = (((processing_template_decl && !DECL_COROUTINE_P(fndecl))
|| is_valid_constexpr_fn (fndecl, /*complain*/false))
&& potential_constant_expression (DECL_SAVED_TREE (fndecl)));
diff --git a/gcc/testsuite/g++.dg/coroutines/pr96251.C
b/gcc/testsuite/g++.dg/coroutines/pr96251.C
new file mode 100644
index 00000000000..6824d783d5f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr96251.C
@@ -0,0 +1,22 @@
+#include <coroutine>
+
+struct coroutine {
+ struct promise_type {
+ auto get_return_object() { return coroutine(); }
+ auto initial_suspend() { return std::suspend_always(); }
+ auto yield_value(int) { return std::suspend_always(); }
+ void return_void() {}
+ auto final_suspend() noexcept { return std::suspend_always(); }
+ void unhandled_exception() {}
+ };
+};
+
+int main() {
+ auto f = [](auto max) -> coroutine {
+ for (int i = 0; i < max; ++i) {
+ co_yield i;
+ }
+ };
+
+ f(10);
+}