forked from OSchip/llvm-project
[coroutines] Build fallthrough and set_exception statements.
Summary: This patch adds semantic checking and building of the fall-through `co_return;` statement as well as the `p.set_exception(std::current_exception())` call for handling uncaught exceptions. The fall-through statement is built and checked according to: > [dcl.fct.def.coroutine]/4 > The unqualified-ids return_void and return_value are looked up in the scope of class P. If > both are found, the program is ill-formed. If the unqualified-id return_void is found, flowing > off the end of a coroutine is equivalent to a co_return with no operand. Otherwise, flowing off > the end of a coroutine results in undefined behavior. Similarly the `set_exception` call is only built when that unqualified-id is found in the scope of class P. Additionally this patch adds fall-through warnings for non-void returning coroutines. Since it's surprising undefined behavior I thought it would be important to add the warning right away. Reviewers: majnemer, GorNishanov, rsmith Subscribers: mehdi_amini, cfe-commits Differential Revision: https://reviews.llvm.org/D25349 llvm-svn: 285271
This commit is contained in:
parent
ec47065795
commit
709d1b30ab
|
@ -405,10 +405,13 @@ public:
|
||||||
|
|
||||||
SourceLocation getLocStart() const LLVM_READONLY { return CoreturnLoc; }
|
SourceLocation getLocStart() const LLVM_READONLY { return CoreturnLoc; }
|
||||||
SourceLocation getLocEnd() const LLVM_READONLY {
|
SourceLocation getLocEnd() const LLVM_READONLY {
|
||||||
return getOperand()->getLocEnd();
|
return getOperand() ? getOperand()->getLocEnd() : getLocStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
child_range children() {
|
child_range children() {
|
||||||
|
if (!getOperand())
|
||||||
|
return child_range(SubStmts + SubStmt::PromiseCall,
|
||||||
|
SubStmts + SubStmt::Count);
|
||||||
return child_range(SubStmts, SubStmts + SubStmt::Count);
|
return child_range(SubStmts, SubStmts + SubStmt::Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -512,6 +512,12 @@ def err_maybe_falloff_nonvoid_block : Error<
|
||||||
"control may reach end of non-void block">;
|
"control may reach end of non-void block">;
|
||||||
def err_falloff_nonvoid_block : Error<
|
def err_falloff_nonvoid_block : Error<
|
||||||
"control reaches end of non-void block">;
|
"control reaches end of non-void block">;
|
||||||
|
def warn_maybe_falloff_nonvoid_coroutine : Warning<
|
||||||
|
"control may reach end of non-void coroutine">,
|
||||||
|
InGroup<ReturnType>;
|
||||||
|
def warn_falloff_nonvoid_coroutine : Warning<
|
||||||
|
"control reaches end of non-void coroutine">,
|
||||||
|
InGroup<ReturnType>;
|
||||||
def warn_suggest_noreturn_function : Warning<
|
def warn_suggest_noreturn_function : Warning<
|
||||||
"%select{function|method}0 %1 could be declared with attribute 'noreturn'">,
|
"%select{function|method}0 %1 could be declared with attribute 'noreturn'">,
|
||||||
InGroup<MissingNoreturn>, DefaultIgnore;
|
InGroup<MissingNoreturn>, DefaultIgnore;
|
||||||
|
@ -8643,6 +8649,13 @@ def err_implied_std_coroutine_traits_promise_type_not_class : Error<
|
||||||
def err_coroutine_traits_missing_specialization : Error<
|
def err_coroutine_traits_missing_specialization : Error<
|
||||||
"this function cannot be a coroutine: missing definition of "
|
"this function cannot be a coroutine: missing definition of "
|
||||||
"specialization %q0">;
|
"specialization %q0">;
|
||||||
|
def err_implied_std_current_exception_not_found : Error<
|
||||||
|
"you need to include <exception> before defining a coroutine that implicitly "
|
||||||
|
"uses 'set_exception'">;
|
||||||
|
def err_malformed_std_current_exception : Error<
|
||||||
|
"'std::current_exception' must be a function">;
|
||||||
|
def err_coroutine_promise_return_ill_formed : Error<
|
||||||
|
"%0 declares both 'return_value' and 'return_void'">;
|
||||||
}
|
}
|
||||||
|
|
||||||
let CategoryName = "Documentation Issue" in {
|
let CategoryName = "Documentation Issue" in {
|
||||||
|
|
|
@ -365,7 +365,7 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
|
||||||
|
|
||||||
CFGStmt CS = ri->castAs<CFGStmt>();
|
CFGStmt CS = ri->castAs<CFGStmt>();
|
||||||
const Stmt *S = CS.getStmt();
|
const Stmt *S = CS.getStmt();
|
||||||
if (isa<ReturnStmt>(S)) {
|
if (isa<ReturnStmt>(S) || isa<CoreturnStmt>(S)) {
|
||||||
HasLiveReturn = true;
|
HasLiveReturn = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,7 @@ struct CheckFallThroughDiagnostics {
|
||||||
unsigned diag_AlwaysFallThrough_HasNoReturn;
|
unsigned diag_AlwaysFallThrough_HasNoReturn;
|
||||||
unsigned diag_AlwaysFallThrough_ReturnsNonVoid;
|
unsigned diag_AlwaysFallThrough_ReturnsNonVoid;
|
||||||
unsigned diag_NeverFallThroughOrReturn;
|
unsigned diag_NeverFallThroughOrReturn;
|
||||||
enum { Function, Block, Lambda } funMode;
|
enum { Function, Block, Lambda, Coroutine } funMode;
|
||||||
SourceLocation FuncLoc;
|
SourceLocation FuncLoc;
|
||||||
|
|
||||||
static CheckFallThroughDiagnostics MakeForFunction(const Decl *Func) {
|
static CheckFallThroughDiagnostics MakeForFunction(const Decl *Func) {
|
||||||
|
@ -452,6 +452,19 @@ struct CheckFallThroughDiagnostics {
|
||||||
return D;
|
return D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CheckFallThroughDiagnostics MakeForCoroutine(const Decl *Func) {
|
||||||
|
CheckFallThroughDiagnostics D;
|
||||||
|
D.FuncLoc = Func->getLocation();
|
||||||
|
D.diag_MaybeFallThrough_HasNoReturn = 0;
|
||||||
|
D.diag_MaybeFallThrough_ReturnsNonVoid =
|
||||||
|
diag::warn_maybe_falloff_nonvoid_coroutine;
|
||||||
|
D.diag_AlwaysFallThrough_HasNoReturn = 0;
|
||||||
|
D.diag_AlwaysFallThrough_ReturnsNonVoid =
|
||||||
|
diag::warn_falloff_nonvoid_coroutine;
|
||||||
|
D.funMode = Coroutine;
|
||||||
|
return D;
|
||||||
|
}
|
||||||
|
|
||||||
static CheckFallThroughDiagnostics MakeForBlock() {
|
static CheckFallThroughDiagnostics MakeForBlock() {
|
||||||
CheckFallThroughDiagnostics D;
|
CheckFallThroughDiagnostics D;
|
||||||
D.diag_MaybeFallThrough_HasNoReturn =
|
D.diag_MaybeFallThrough_HasNoReturn =
|
||||||
|
@ -494,7 +507,13 @@ struct CheckFallThroughDiagnostics {
|
||||||
(!ReturnsVoid ||
|
(!ReturnsVoid ||
|
||||||
D.isIgnored(diag::warn_suggest_noreturn_block, FuncLoc));
|
D.isIgnored(diag::warn_suggest_noreturn_block, FuncLoc));
|
||||||
}
|
}
|
||||||
|
if (funMode == Coroutine) {
|
||||||
|
return (ReturnsVoid ||
|
||||||
|
D.isIgnored(diag::warn_maybe_falloff_nonvoid_function, FuncLoc) ||
|
||||||
|
D.isIgnored(diag::warn_maybe_falloff_nonvoid_coroutine,
|
||||||
|
FuncLoc)) &&
|
||||||
|
(!HasNoReturn);
|
||||||
|
}
|
||||||
// For blocks / lambdas.
|
// For blocks / lambdas.
|
||||||
return ReturnsVoid && !HasNoReturn;
|
return ReturnsVoid && !HasNoReturn;
|
||||||
}
|
}
|
||||||
|
@ -514,11 +533,14 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
|
||||||
bool ReturnsVoid = false;
|
bool ReturnsVoid = false;
|
||||||
bool HasNoReturn = false;
|
bool HasNoReturn = false;
|
||||||
|
|
||||||
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
|
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
|
||||||
ReturnsVoid = FD->getReturnType()->isVoidType();
|
if (const auto *CBody = dyn_cast<CoroutineBodyStmt>(Body))
|
||||||
|
ReturnsVoid = CBody->getFallthroughHandler() != nullptr;
|
||||||
|
else
|
||||||
|
ReturnsVoid = FD->getReturnType()->isVoidType();
|
||||||
HasNoReturn = FD->isNoReturn();
|
HasNoReturn = FD->isNoReturn();
|
||||||
}
|
}
|
||||||
else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
|
else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
|
||||||
ReturnsVoid = MD->getReturnType()->isVoidType();
|
ReturnsVoid = MD->getReturnType()->isVoidType();
|
||||||
HasNoReturn = MD->hasAttr<NoReturnAttr>();
|
HasNoReturn = MD->hasAttr<NoReturnAttr>();
|
||||||
}
|
}
|
||||||
|
@ -1986,13 +2008,22 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
|
||||||
|
|
||||||
// Warning: check missing 'return'
|
// Warning: check missing 'return'
|
||||||
if (P.enableCheckFallThrough) {
|
if (P.enableCheckFallThrough) {
|
||||||
|
auto IsCoro = [&]() {
|
||||||
|
if (auto *FD = dyn_cast<FunctionDecl>(D))
|
||||||
|
if (FD->getBody() && isa<CoroutineBodyStmt>(FD->getBody()))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
const CheckFallThroughDiagnostics &CD =
|
const CheckFallThroughDiagnostics &CD =
|
||||||
(isa<BlockDecl>(D) ? CheckFallThroughDiagnostics::MakeForBlock()
|
(isa<BlockDecl>(D)
|
||||||
: (isa<CXXMethodDecl>(D) &&
|
? CheckFallThroughDiagnostics::MakeForBlock()
|
||||||
cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
|
: (isa<CXXMethodDecl>(D) &&
|
||||||
cast<CXXMethodDecl>(D)->getParent()->isLambda())
|
cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
|
||||||
? CheckFallThroughDiagnostics::MakeForLambda()
|
cast<CXXMethodDecl>(D)->getParent()->isLambda())
|
||||||
: CheckFallThroughDiagnostics::MakeForFunction(D));
|
? CheckFallThroughDiagnostics::MakeForLambda()
|
||||||
|
: (IsCoro()
|
||||||
|
? CheckFallThroughDiagnostics::MakeForCoroutine(D)
|
||||||
|
: CheckFallThroughDiagnostics::MakeForFunction(D)));
|
||||||
CheckFallThroughForBody(S, D, Body, blkExpr, CD, AC);
|
CheckFallThroughForBody(S, D, Body, blkExpr, CD, AC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,34 @@ StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E) {
|
||||||
return Res;
|
return Res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ExprResult buildStdCurrentExceptionCall(Sema &S, SourceLocation Loc) {
|
||||||
|
NamespaceDecl *Std = S.getStdNamespace();
|
||||||
|
if (!Std) {
|
||||||
|
S.Diag(Loc, diag::err_implied_std_current_exception_not_found);
|
||||||
|
return ExprError();
|
||||||
|
}
|
||||||
|
LookupResult Result(S, &S.PP.getIdentifierTable().get("current_exception"),
|
||||||
|
Loc, Sema::LookupOrdinaryName);
|
||||||
|
if (!S.LookupQualifiedName(Result, Std)) {
|
||||||
|
S.Diag(Loc, diag::err_implied_std_current_exception_not_found);
|
||||||
|
return ExprError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME The STL is free to provide more than one overload.
|
||||||
|
FunctionDecl *FD = Result.getAsSingle<FunctionDecl>();
|
||||||
|
if (!FD) {
|
||||||
|
S.Diag(Loc, diag::err_malformed_std_current_exception);
|
||||||
|
return ExprError();
|
||||||
|
}
|
||||||
|
ExprResult Res = S.BuildDeclRefExpr(FD, FD->getType(), VK_LValue, Loc);
|
||||||
|
Res = S.ActOnCallExpr(/*Scope*/ nullptr, Res.get(), Loc, None, Loc);
|
||||||
|
if (Res.isInvalid()) {
|
||||||
|
S.Diag(Loc, diag::err_malformed_std_current_exception);
|
||||||
|
return ExprError();
|
||||||
|
}
|
||||||
|
return Res;
|
||||||
|
}
|
||||||
|
|
||||||
void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
|
void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
|
||||||
FunctionScopeInfo *Fn = getCurFunction();
|
FunctionScopeInfo *Fn = getCurFunction();
|
||||||
assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine");
|
assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine");
|
||||||
|
@ -432,10 +460,59 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
|
||||||
if (FinalSuspend.isInvalid())
|
if (FinalSuspend.isInvalid())
|
||||||
return FD->setInvalidDecl();
|
return FD->setInvalidDecl();
|
||||||
|
|
||||||
// FIXME: Perform analysis of set_exception call.
|
// Try to form 'p.return_void();' expression statement to handle
|
||||||
|
|
||||||
// FIXME: Try to form 'p.return_void();' expression statement to handle
|
|
||||||
// control flowing off the end of the coroutine.
|
// control flowing off the end of the coroutine.
|
||||||
|
// Also try to form 'p.set_exception(std::current_exception());' to handle
|
||||||
|
// uncaught exceptions.
|
||||||
|
ExprResult SetException;
|
||||||
|
StmtResult Fallthrough;
|
||||||
|
if (Fn->CoroutinePromise &&
|
||||||
|
!Fn->CoroutinePromise->getType()->isDependentType()) {
|
||||||
|
CXXRecordDecl *RD = Fn->CoroutinePromise->getType()->getAsCXXRecordDecl();
|
||||||
|
assert(RD && "Type should have already been checked");
|
||||||
|
// [dcl.fct.def.coroutine]/4
|
||||||
|
// The unqualified-ids 'return_void' and 'return_value' are looked up in
|
||||||
|
// the scope of class P. If both are found, the program is ill-formed.
|
||||||
|
DeclarationName RVoidDN = PP.getIdentifierInfo("return_void");
|
||||||
|
LookupResult RVoidResult(*this, RVoidDN, Loc, Sema::LookupMemberName);
|
||||||
|
const bool HasRVoid = LookupQualifiedName(RVoidResult, RD);
|
||||||
|
|
||||||
|
DeclarationName RValueDN = PP.getIdentifierInfo("return_value");
|
||||||
|
LookupResult RValueResult(*this, RValueDN, Loc, Sema::LookupMemberName);
|
||||||
|
const bool HasRValue = LookupQualifiedName(RValueResult, RD);
|
||||||
|
|
||||||
|
if (HasRVoid && HasRValue) {
|
||||||
|
// FIXME Improve this diagnostic
|
||||||
|
Diag(FD->getLocation(), diag::err_coroutine_promise_return_ill_formed)
|
||||||
|
<< RD;
|
||||||
|
return FD->setInvalidDecl();
|
||||||
|
} else if (HasRVoid) {
|
||||||
|
// If the unqualified-id return_void is found, flowing off the end of a
|
||||||
|
// coroutine is equivalent to a co_return with no operand. Otherwise,
|
||||||
|
// flowing off the end of a coroutine results in undefined behavior.
|
||||||
|
Fallthrough = BuildCoreturnStmt(FD->getLocation(), nullptr);
|
||||||
|
Fallthrough = ActOnFinishFullStmt(Fallthrough.get());
|
||||||
|
if (Fallthrough.isInvalid())
|
||||||
|
return FD->setInvalidDecl();
|
||||||
|
}
|
||||||
|
|
||||||
|
// [dcl.fct.def.coroutine]/3
|
||||||
|
// The unqualified-id set_exception is found in the scope of P by class
|
||||||
|
// member access lookup (3.4.5).
|
||||||
|
DeclarationName SetExDN = PP.getIdentifierInfo("set_exception");
|
||||||
|
LookupResult SetExResult(*this, SetExDN, Loc, Sema::LookupMemberName);
|
||||||
|
if (LookupQualifiedName(SetExResult, RD)) {
|
||||||
|
// Form the call 'p.set_exception(std::current_exception())'
|
||||||
|
SetException = buildStdCurrentExceptionCall(*this, Loc);
|
||||||
|
if (SetException.isInvalid())
|
||||||
|
return FD->setInvalidDecl();
|
||||||
|
Expr *E = SetException.get();
|
||||||
|
SetException = buildPromiseCall(*this, Fn, Loc, "set_exception", E);
|
||||||
|
SetException = ActOnFinishFullExpr(SetException.get(), Loc);
|
||||||
|
if (SetException.isInvalid())
|
||||||
|
return FD->setInvalidDecl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build implicit 'p.get_return_object()' expression and form initialization
|
// Build implicit 'p.get_return_object()' expression and form initialization
|
||||||
// of return type from it.
|
// of return type from it.
|
||||||
|
@ -462,6 +539,5 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
|
||||||
// Build body for the coroutine wrapper statement.
|
// Build body for the coroutine wrapper statement.
|
||||||
Body = new (Context) CoroutineBodyStmt(
|
Body = new (Context) CoroutineBodyStmt(
|
||||||
Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(),
|
Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(),
|
||||||
/*SetException*/nullptr, /*Fallthrough*/nullptr,
|
SetException.get(), Fallthrough.get(), ReturnObject.get(), ParamMoves);
|
||||||
ReturnObject.get(), ParamMoves);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6654,6 +6654,7 @@ template<typename Derived>
|
||||||
StmtResult
|
StmtResult
|
||||||
TreeTransform<Derived>::TransformCoroutineBodyStmt(CoroutineBodyStmt *S) {
|
TreeTransform<Derived>::TransformCoroutineBodyStmt(CoroutineBodyStmt *S) {
|
||||||
// The coroutine body should be re-formed by the caller if necessary.
|
// The coroutine body should be re-formed by the caller if necessary.
|
||||||
|
// FIXME: The coroutine body is always rebuilt by ActOnFinishFunctionBody
|
||||||
return getDerived().TransformStmt(S->getBody());
|
return getDerived().TransformStmt(S->getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value
|
||||||
|
|
||||||
|
struct awaitable {
|
||||||
|
bool await_ready();
|
||||||
|
void await_suspend(); // FIXME: coroutine_handle
|
||||||
|
void await_resume();
|
||||||
|
} a;
|
||||||
|
|
||||||
|
struct suspend_always {
|
||||||
|
bool await_ready() { return false; }
|
||||||
|
void await_suspend() {}
|
||||||
|
void await_resume() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct suspend_never {
|
||||||
|
bool await_ready() { return true; }
|
||||||
|
void await_suspend() {}
|
||||||
|
void await_resume() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
namespace experimental {
|
||||||
|
|
||||||
|
template <class Ret, typename... T>
|
||||||
|
struct coroutine_traits { using promise_type = typename Ret::promise_type; };
|
||||||
|
|
||||||
|
template <class Promise = void>
|
||||||
|
struct coroutine_handle {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct promise_void {
|
||||||
|
void get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_void();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct promise_float {
|
||||||
|
float get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_void();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct promise_int {
|
||||||
|
int get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_value(int);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
struct std::experimental::coroutine_traits<void, T...> { using promise_type = promise_void; };
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
struct std::experimental::coroutine_traits<float, T...> { using promise_type = promise_float; };
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
struct std::experimental::coroutine_traits<int, T...> { using promise_type = promise_int; };
|
||||||
|
|
||||||
|
void test0() { co_await a; }
|
||||||
|
float test1() { co_await a; }
|
||||||
|
|
||||||
|
int test2() {
|
||||||
|
co_await a;
|
||||||
|
} // expected-warning {{control reaches end of non-void coroutine}}
|
||||||
|
|
||||||
|
int test3() {
|
||||||
|
co_await a;
|
||||||
|
b:
|
||||||
|
goto b;
|
||||||
|
}
|
|
@ -118,9 +118,6 @@ struct promise_void {
|
||||||
void get_return_object();
|
void get_return_object();
|
||||||
suspend_always initial_suspend();
|
suspend_always initial_suspend();
|
||||||
suspend_always final_suspend();
|
suspend_always final_suspend();
|
||||||
awaitable yield_value(int);
|
|
||||||
awaitable yield_value(yielded_thing);
|
|
||||||
not_awaitable yield_value(void());
|
|
||||||
void return_void();
|
void return_void();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -313,6 +310,47 @@ coro<bad_promise_5> bad_final_suspend() { // expected-error {{no member named 'a
|
||||||
co_await a;
|
co_await a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct bad_promise_6 {
|
||||||
|
coro<bad_promise_6> get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_void();
|
||||||
|
void return_value(int) const;
|
||||||
|
void return_value(int);
|
||||||
|
};
|
||||||
|
coro<bad_promise_6> bad_implicit_return() { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}}
|
||||||
|
co_await a;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bad_promise_7 {
|
||||||
|
coro<bad_promise_7> get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_void();
|
||||||
|
void set_exception(int *);
|
||||||
|
};
|
||||||
|
coro<bad_promise_7> no_std_current_exc() {
|
||||||
|
// expected-error@-1 {{you need to include <exception> before defining a coroutine that implicitly uses 'set_exception'}}
|
||||||
|
co_await a;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
int *current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bad_promise_8 {
|
||||||
|
coro<bad_promise_8> get_return_object();
|
||||||
|
suspend_always initial_suspend();
|
||||||
|
suspend_always final_suspend();
|
||||||
|
void return_void();
|
||||||
|
void set_exception(); // expected-note {{function not viable}}
|
||||||
|
void set_exception(int *) __attribute__((unavailable)); // expected-note {{explicitly made unavailable}}
|
||||||
|
void set_exception(void *); // expected-note {{candidate function}}
|
||||||
|
};
|
||||||
|
coro<bad_promise_8> calls_set_exception() {
|
||||||
|
// expected-error@-1 {{call to unavailable member function 'set_exception'}}
|
||||||
|
co_await a;
|
||||||
|
}
|
||||||
|
|
||||||
template<> struct std::experimental::coroutine_traits<int, int, const char**>
|
template<> struct std::experimental::coroutine_traits<int, int, const char**>
|
||||||
{ using promise_type = promise; };
|
{ using promise_type = promise; };
|
||||||
|
|
Loading…
Reference in New Issue