Skip to content

Commit db1375f

Browse files
Surface error for plain return statement in coroutine earlier (#100985)
When a plain return statement was used in a coroutine, the error "return statement not allowed in coroutine" was surfaced too late (e.g. after other errors in the return statement). Surfacing it earlier now, to make the issue more obvious.
1 parent adac04f commit db1375f

File tree

3 files changed

+104
-10
lines changed

3 files changed

+104
-10
lines changed

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,19 @@ bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
684684
return ThrowingDecls.empty();
685685
}
686686

687+
// [stmt.return.coroutine]p1:
688+
// A coroutine shall not enclose a return statement ([stmt.return]).
689+
static void checkReturnStmtInCoroutine(Sema &S, FunctionScopeInfo *FSI) {
690+
assert(FSI && "FunctionScopeInfo is null");
691+
assert(FSI->FirstCoroutineStmtLoc.isValid() &&
692+
"first coroutine location not set");
693+
if (FSI->FirstReturnLoc.isInvalid())
694+
return;
695+
S.Diag(FSI->FirstReturnLoc, diag::err_return_in_coroutine);
696+
S.Diag(FSI->FirstCoroutineStmtLoc, diag::note_declared_coroutine_here)
697+
<< FSI->getFirstCoroutineStmtKeyword();
698+
}
699+
687700
bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
688701
StringRef Keyword) {
689702
// Ignore previous expr evaluation contexts.
@@ -694,6 +707,10 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
694707
auto *ScopeInfo = getCurFunction();
695708
assert(ScopeInfo->CoroutinePromise);
696709

710+
// Avoid duplicate errors, report only on first keyword.
711+
if (ScopeInfo->FirstCoroutineStmtLoc == KWLoc)
712+
checkReturnStmtInCoroutine(*this, ScopeInfo);
713+
697714
// If we have existing coroutine statements then we have already built
698715
// the initial and final suspend points.
699716
if (!ScopeInfo->NeedsCoroutineSuspends)
@@ -801,6 +818,7 @@ ExprResult Sema::ActOnCoawaitExpr(Scope *S, SourceLocation Loc, Expr *E) {
801818
if (R.isInvalid()) return ExprError();
802819
E = R.get();
803820
}
821+
804822
ExprResult Lookup = BuildOperatorCoawaitLookupExpr(S, Loc);
805823
if (Lookup.isInvalid())
806824
return ExprError();
@@ -1118,16 +1136,6 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
11181136
if (Fn->FirstVLALoc.isValid())
11191137
Diag(Fn->FirstVLALoc, diag::err_vla_in_coroutine_unsupported);
11201138

1121-
// [stmt.return.coroutine]p1:
1122-
// A coroutine shall not enclose a return statement ([stmt.return]).
1123-
if (Fn->FirstReturnLoc.isValid()) {
1124-
assert(Fn->FirstCoroutineStmtLoc.isValid() &&
1125-
"first coroutine location not set");
1126-
Diag(Fn->FirstReturnLoc, diag::err_return_in_coroutine);
1127-
Diag(Fn->FirstCoroutineStmtLoc, diag::note_declared_coroutine_here)
1128-
<< Fn->getFirstCoroutineStmtKeyword();
1129-
}
1130-
11311139
// Coroutines will get splitted into pieces. The GNU address of label
11321140
// extension wouldn't be meaningful in coroutines.
11331141
for (AddrLabelExpr *ALE : Fn->AddrLabels)

clang/lib/Sema/SemaStmt.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3747,6 +3747,16 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp,
37473747
Diag(ReturnLoc, diag::err_acc_branch_in_out_compute_construct)
37483748
<< /*return*/ 1 << /*out of */ 0);
37493749

3750+
// using plain return in a coroutine is not allowed.
3751+
FunctionScopeInfo *FSI = getCurFunction();
3752+
if (FSI->FirstReturnLoc.isInvalid() && FSI->isCoroutine()) {
3753+
assert(FSI->FirstCoroutineStmtLoc.isValid() &&
3754+
"first coroutine location not set");
3755+
Diag(ReturnLoc, diag::err_return_in_coroutine);
3756+
Diag(FSI->FirstCoroutineStmtLoc, diag::note_declared_coroutine_here)
3757+
<< FSI->getFirstCoroutineStmtKeyword();
3758+
}
3759+
37503760
StmtResult R =
37513761
BuildReturnStmt(ReturnLoc, RetVal.get(), /*AllowRecovery=*/true);
37523762
if (R.isInvalid() || ExprEvalContexts.back().isDiscardedStatementContext())

clang/test/SemaCXX/coroutines.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=expected,cxx20_23,cxx23 %s -fcxx-exceptions -fexceptions -Wunused-result
55
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=expected,cxx14_20,cxx20_23 %s -fcxx-exceptions -fexceptions -Wunused-result
66

7+
// Run without -verify to check the order of errors we show.
8+
// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s -fcxx-exceptions -fexceptions -Wunused-result 2>&1 | FileCheck %s
9+
710
void no_coroutine_traits_bad_arg_await() {
811
co_await a; // expected-error {{include <coroutine>}}
912
// expected-error@-1 {{use of undeclared identifier 'a'}}
@@ -154,12 +157,15 @@ namespace std {
154157
template <class PromiseType = void>
155158
struct coroutine_handle {
156159
static coroutine_handle from_address(void *) noexcept;
160+
static coroutine_handle from_promise(PromiseType &promise);
157161
};
158162
template <>
159163
struct coroutine_handle<void> {
160164
template <class PromiseType>
161165
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
162166
static coroutine_handle from_address(void *) noexcept;
167+
template <class PromiseType>
168+
static coroutine_handle from_promise(PromiseType &promise);
163169
};
164170
} // namespace std
165171

@@ -206,6 +212,22 @@ void mixed_yield_invalid() {
206212
return; // expected-error {{return statement not allowed in coroutine}}
207213
}
208214

215+
void mixed_yield_return_first(bool b) {
216+
if (b) {
217+
return; // expected-error {{return statement not allowed in coroutine}}
218+
}
219+
co_yield 0; // expected-note {{function is a coroutine due to use of 'co_yield'}}
220+
}
221+
222+
template<typename T>
223+
void mixed_return_for_range(bool b, T t) {
224+
if (b) {
225+
return; // expected-error {{return statement not allowed in coroutine}}
226+
}
227+
for co_await (auto i : t){}; // expected-warning {{'for co_await' belongs to CoroutineTS instead of C++20, which is deprecated}}
228+
// expected-note@-1 {{function is a coroutine due to use of 'co_await'}}
229+
}
230+
209231
template <class T>
210232
void mixed_yield_template(T) {
211233
co_yield blah; // expected-error {{use of undeclared identifier}}
@@ -264,6 +286,13 @@ void mixed_coreturn(void_tag, bool b) {
264286
return; // expected-error {{not allowed in coroutine}}
265287
}
266288

289+
void mixed_coreturn_return_first(void_tag, bool b) {
290+
if (b)
291+
return; // expected-error {{not allowed in coroutine}}
292+
else
293+
co_return; // expected-note {{use of 'co_return'}}
294+
}
295+
267296
void mixed_coreturn_invalid(bool b) {
268297
if (b)
269298
co_return; // expected-note {{use of 'co_return'}}
@@ -291,6 +320,53 @@ void mixed_coreturn_template2(bool b, T) {
291320
return; // expected-error {{not allowed in coroutine}}
292321
}
293322

323+
struct promise_handle;
324+
325+
struct Handle : std::coroutine_handle<promise_handle> { // expected-note 4{{not viable}}
326+
// expected-note@-1 4{{not viable}}
327+
using promise_type = promise_handle;
328+
};
329+
330+
struct promise_handle {
331+
Handle get_return_object() noexcept {
332+
{ return Handle(std::coroutine_handle<Handle::promise_type>::from_promise(*this)); }
333+
}
334+
suspend_never initial_suspend() const noexcept { return {}; }
335+
suspend_never final_suspend() const noexcept { return {}; }
336+
void return_void() const noexcept {}
337+
void unhandled_exception() const noexcept {}
338+
};
339+
340+
Handle mixed_return_value() {
341+
co_await a; // expected-note {{function is a coroutine due to use of 'co_await' here}}
342+
return 0; // expected-error {{return statement not allowed in coroutine}}
343+
// expected-error@-1 {{no viable conversion from returned value of type}}
344+
// Check that we first show that return is not allowed in coroutine.
345+
// The error about bad conversion is most likely spurious so we prefer to have it afterwards.
346+
// CHECK-NOT: error: no viable conversion from returned value of type
347+
// CHECK: error: return statement not allowed in coroutine
348+
// CHECK: error: no viable conversion from returned value of type
349+
}
350+
351+
Handle mixed_return_value_return_first(bool b) {
352+
if (b) {
353+
return 0; // expected-error {{no viable conversion from returned value of type}}
354+
// expected-error@-1 {{return statement not allowed in coroutine}}
355+
}
356+
co_await a; // expected-note {{function is a coroutine due to use of 'co_await' here}}
357+
co_return 0; // expected-error {{no member named 'return_value' in 'promise_handle'}}
358+
}
359+
360+
Handle mixed_multiple_returns(bool b) {
361+
if (b) {
362+
return 0; // expected-error {{no viable conversion from returned value of type}}
363+
// expected-error@-1 {{return statement not allowed in coroutine}}
364+
}
365+
co_await a; // expected-note {{function is a coroutine due to use of 'co_await' here}}
366+
// The error 'return statement not allowed in coroutine' should appear only once.
367+
return 0; // expected-error {{no viable conversion from returned value of type}}
368+
}
369+
294370
struct CtorDtor {
295371
CtorDtor() {
296372
co_yield 0; // expected-error {{'co_yield' cannot be used in a constructor}}

0 commit comments

Comments
 (0)