Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -6303,6 +6303,8 @@ InGroup>; def note_member_declared_here : Note< "member %0 declared here">; +def note_member_first_declared_here : Note< + "member %0 first declared here">; def err_decrement_bool : Error<"cannot decrement expression of type bool">; def warn_increment_bool : Warning< "incrementing expression of type bool is deprecated and " @@ -8918,8 +8920,6 @@ "return statement not allowed in coroutine; did you mean 'co_return'?">; def note_declared_coroutine_here : Note< "function is a coroutine due to use of '%0' here">; -def note_promise_member_declared_here : Note< - "'%0' is declared here">; def err_coroutine_objc_method : Error< "Objective-C methods as coroutines are not yet supported">; def err_coroutine_unevaluated_context : Error< @@ -8954,8 +8954,10 @@ def err_coroutine_type_missing_specialization : Error< "this function cannot be a coroutine: missing definition of " "specialization %q0">; -def err_coroutine_promise_return_ill_formed : Error< - "%0 declares both 'return_value' and 'return_void'">; +def err_coroutine_promise_incompatible_return_functions : Error< + "the coroutine promise type %0 declares both 'return_value' and 'return_void'">; +def err_coroutine_promise_requires_return_function : Error< + "the coroutine promise type %0 must declare either 'return_value' or 'return_void'">; def note_coroutine_promise_implicit_await_transform_required_here : Note< "call to 'await_transform' implicitly required by 'co_await' here">; def note_coroutine_promise_suspend_implicitly_required : Note< Index: lib/Sema/SemaCoroutine.cpp =================================================================== --- lib/Sema/SemaCoroutine.cpp +++ lib/Sema/SemaCoroutine.cpp @@ -23,14 +23,22 @@ using namespace clang; using namespace sema; -static bool lookupMember(Sema &S, const char *Name, CXXRecordDecl *RD, - SourceLocation Loc) { +static LookupResult lookupMember(Sema &S, const char *Name, CXXRecordDecl *RD, + SourceLocation Loc, bool &Res) { DeclarationName DN = S.PP.getIdentifierInfo(Name); LookupResult LR(S, DN, Loc, Sema::LookupMemberName); // Suppress diagnostics when a private member is selected. The same warnings // will be produced again when building the call. LR.suppressDiagnostics(); - return S.LookupQualifiedName(LR, RD); + Res = S.LookupQualifiedName(LR, RD); + return LR; +} + +static bool lookupMember(Sema &S, const char *Name, CXXRecordDecl *RD, + SourceLocation Loc) { + bool Res; + lookupMember(S, Name, RD, Loc, Res); + return Res; } /// Look up the std::coroutine_traits<...>::promise_type for the given @@ -861,9 +869,8 @@ StmtResult ReturnStmt = S.BuildReturnStmt(Loc, ReturnObjectOnAllocationFailure.get()); if (ReturnStmt.isInvalid()) { - S.Diag(Found.getFoundDecl()->getLocation(), - diag::note_promise_member_declared_here) - << DN.getAsString(); + S.Diag(Found.getFoundDecl()->getLocation(), diag::note_member_declared_here) + << DN; S.Diag(Fn.FirstCoroutineStmtLoc, diag::note_declared_coroutine_here) << Fn.getFirstCoroutineStmtKeyword(); return false; @@ -993,13 +1000,32 @@ // [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. - const bool HasRVoid = lookupMember(S, "return_void", PromiseRecordDecl, Loc); - const bool HasRValue = lookupMember(S, "return_value", PromiseRecordDecl, Loc); + bool HasRVoid, HasRValue; + LookupResult LRVoid = + lookupMember(S, "return_void", PromiseRecordDecl, Loc, HasRVoid); + LookupResult LRValue = + lookupMember(S, "return_value", PromiseRecordDecl, Loc, HasRValue); StmtResult Fallthrough; if (HasRVoid && HasRValue) { // FIXME Improve this diagnostic - S.Diag(FD.getLocation(), diag::err_coroutine_promise_return_ill_formed) + S.Diag(FD.getLocation(), + diag::err_coroutine_promise_incompatible_return_functions) + << PromiseRecordDecl; + S.Diag(LRVoid.getRepresentativeDecl()->getLocation(), + diag::note_member_first_declared_here) + << LRVoid.getLookupName(); + S.Diag(LRValue.getRepresentativeDecl()->getLocation(), + diag::note_member_first_declared_here) + << LRValue.getLookupName(); + return false; + } else if (!HasRVoid && !HasRValue) { + // FIXME: The PDTS currently specifies this case as UB, not ill-formed. + // However we still diagnose this as an error since until the PDTS is fixed. + S.Diag(FD.getLocation(), + diag::err_coroutine_promise_requires_return_function) + << PromiseRecordDecl; + S.Diag(PromiseRecordDecl->getLocation(), diag::note_defined_here) << PromiseRecordDecl; return false; } else if (HasRVoid) { @@ -1074,8 +1100,8 @@ static void noteMemberDeclaredHere(Sema &S, Expr *E, FunctionScopeInfo &Fn) { if (auto *MbrRef = dyn_cast(E)) { auto *MethodDecl = MbrRef->getMethodDecl(); - S.Diag(MethodDecl->getLocation(), diag::note_promise_member_declared_here) - << MethodDecl->getName(); + S.Diag(MethodDecl->getLocation(), diag::note_member_declared_here) + << MethodDecl; } S.Diag(Fn.FirstCoroutineStmtLoc, diag::note_declared_coroutine_here) << Fn.getFirstCoroutineStmtKeyword(); Index: test/SemaCXX/coreturn.cpp =================================================================== --- test/SemaCXX/coreturn.cpp +++ test/SemaCXX/coreturn.cpp @@ -138,16 +138,3 @@ if (b) co_return 42; } // expected-warning {{control may reach end of coroutine}} - -// FIXME: Promise types that declare neither return_value nor return_void -// should be ill-formed. This test should be removed when that is fixed. -VoidTagNoReturn test12() { - co_await a; -} // expected-warning {{control reaches end of coroutine}} - -// FIXME: Promise types that declare neither return_value nor return_void -// should be ill-formed. This test should be removed when that is fixed. -VoidTagNoReturn test13(bool b) { - if (b) - co_await a; -} // expected-warning {{control reaches end of coroutine}} Index: test/SemaCXX/coroutines.cpp =================================================================== --- test/SemaCXX/coroutines.cpp +++ test/SemaCXX/coroutines.cpp @@ -347,6 +347,7 @@ ::adl_ns::coawait_arg_type final_suspend(); transformed await_transform(transform_awaitable); void unhandled_exception(); + void return_void(); }; template struct basic_promise { @@ -355,6 +356,7 @@ awaitable initial_suspend(); awaitable final_suspend(); void unhandled_exception(); + void return_void(); }; awaitable operator co_await(await_arg_1); @@ -473,6 +475,7 @@ suspend_always initial_suspend(); suspend_always final_suspend(); void unhandled_exception(); + void return_void(); }; coro missing_get_return_object() { // expected-error {{no member named 'get_return_object' in 'bad_promise_1'}} co_await a; @@ -483,6 +486,7 @@ // FIXME: We shouldn't offer a typo-correction here! suspend_always final_suspend(); // expected-note {{here}} void unhandled_exception(); + void return_void(); }; // FIXME: This shouldn't happen twice coro missing_initial_suspend() { // expected-error {{no member named 'initial_suspend' in 'bad_promise_2'}} @@ -494,6 +498,7 @@ // FIXME: We shouldn't offer a typo-correction here! suspend_always initial_suspend(); // expected-note {{here}} void unhandled_exception(); + void return_void(); }; coro missing_final_suspend() { // expected-error {{no member named 'final_suspend' in 'bad_promise_3'}} co_await a; @@ -503,6 +508,7 @@ coro get_return_object(); not_awaitable initial_suspend(); suspend_always final_suspend(); + void return_void(); }; // FIXME: This diagnostic is terrible. coro bad_initial_suspend() { // expected-error {{no member named 'await_ready' in 'not_awaitable'}} @@ -514,6 +520,7 @@ coro get_return_object(); suspend_always initial_suspend(); not_awaitable final_suspend(); + void return_void(); }; // FIXME: This diagnostic is terrible. coro bad_final_suspend() { // expected-error {{no member named 'await_ready' in 'not_awaitable'}} @@ -526,8 +533,8 @@ suspend_always initial_suspend(); suspend_always final_suspend(); void unhandled_exception(); - void return_void(); - void return_value(int) const; + void return_void(); // expected-note 2 {{member 'return_void' first declared here}} + void return_value(int) const; // expected-note 2 {{member 'return_value' first declared here}} void return_value(int); }; coro bad_implicit_return() { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}} @@ -751,7 +758,7 @@ template<> struct std::experimental::coroutine_traits { struct promise_type { - void get_return_object() {} //expected-note {{'get_return_object' is declared here}} + void get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } void return_void() {} @@ -768,7 +775,7 @@ template<> struct std::experimental::coroutine_traits { struct promise_type { - void* get_return_object() {} //expected-note {{'get_return_object' is declared here}} + void *get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } void return_void() {} @@ -786,7 +793,7 @@ struct std::experimental::coroutine_traits { struct promise_type { int get_return_object() {} - static void get_return_object_on_allocation_failure() {} //expected-note {{'get_return_object_on_allocation_failure' is declared here}} + static void get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared here}} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } void return_void() {} @@ -805,7 +812,7 @@ struct std::experimental::coroutine_traits { struct promise_type { int get_return_object() {} - static char* get_return_object_on_allocation_failure() {} //expected-note {{'get_return_object_on_allocation_failure' is declared here}} + static char *get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared}} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } void return_void() {} @@ -818,14 +825,15 @@ co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} } -struct bad_promise_no_return_func { +struct bad_promise_no_return_func { // expected-note {{'bad_promise_no_return_func' defined here}} coro get_return_object(); suspend_always initial_suspend(); suspend_always final_suspend(); void unhandled_exception(); }; -// FIXME: Make this ill-formed because the promise type declares -// neither return_value(...) or return_void(). +// FIXME: The PDTS currently specifies this as UB, technically forbidding a +// diagnostic. coro no_return_value_or_return_void() { + // expected-error@-1 {{'bad_promise_no_return_func' must declare either 'return_value' or 'return_void'}} co_await a; }