#include "clang/Sema/SemaInternal.h"
#include "clang/AST/Decl.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Overload.h"
using namespace clang;
using namespace sema;
static QualType lookupPromiseType(Sema &S, const FunctionProtoType *FnType,
SourceLocation Loc) {
NamespaceDecl *Std = S.getStdNamespace();
if (!Std) {
S.Diag(Loc, diag::err_implied_std_coroutine_traits_not_found);
return QualType();
}
LookupResult Result(S, &S.PP.getIdentifierTable().get("coroutine_traits"),
Loc, Sema::LookupOrdinaryName);
if (!S.LookupQualifiedName(Result, Std)) {
S.Diag(Loc, diag::err_implied_std_coroutine_traits_not_found);
return QualType();
}
ClassTemplateDecl *CoroTraits = Result.getAsSingle<ClassTemplateDecl>();
if (!CoroTraits) {
Result.suppressDiagnostics();
NamedDecl *Found = *Result.begin();
S.Diag(Found->getLocation(), diag::err_malformed_std_coroutine_traits);
return QualType();
}
TemplateArgumentListInfo Args(Loc, Loc);
Args.addArgument(TemplateArgumentLoc(
TemplateArgument(FnType->getReturnType()),
S.Context.getTrivialTypeSourceInfo(FnType->getReturnType(), Loc)));
for (QualType T : FnType->getParamTypes())
Args.addArgument(TemplateArgumentLoc(
TemplateArgument(T), S.Context.getTrivialTypeSourceInfo(T, Loc)));
QualType CoroTrait =
S.CheckTemplateIdType(TemplateName(CoroTraits), Loc, Args);
if (CoroTrait.isNull())
return QualType();
if (S.RequireCompleteType(Loc, CoroTrait,
diag::err_coroutine_traits_missing_specialization))
return QualType();
CXXRecordDecl *RD = CoroTrait->getAsCXXRecordDecl();
assert(RD && "specialization of class template is not a class?");
LookupResult R(S, &S.PP.getIdentifierTable().get("promise_type"), Loc,
Sema::LookupOrdinaryName);
S.LookupQualifiedName(R, RD);
auto *Promise = R.getAsSingle<TypeDecl>();
if (!Promise) {
S.Diag(Loc, diag::err_implied_std_coroutine_traits_promise_type_not_found)
<< RD;
return QualType();
}
QualType PromiseType = S.Context.getTypeDeclType(Promise);
if (!PromiseType->getAsCXXRecordDecl()) {
auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, Std);
NNS = NestedNameSpecifier::Create(S.Context, NNS, false,
CoroTrait.getTypePtr());
PromiseType = S.Context.getElaboratedType(ETK_None, NNS, PromiseType);
S.Diag(Loc, diag::err_implied_std_coroutine_traits_promise_type_not_class)
<< PromiseType;
return QualType();
}
return PromiseType;
}
static FunctionScopeInfo *
checkCoroutineContext(Sema &S, SourceLocation Loc, StringRef Keyword) {
if (S.isUnevaluatedContext()) {
S.Diag(Loc, diag::err_coroutine_unevaluated_context) << Keyword;
return nullptr;
}
auto *FD = dyn_cast<FunctionDecl>(S.CurContext);
if (!FD) {
S.Diag(Loc, isa<ObjCMethodDecl>(S.CurContext)
? diag::err_coroutine_objc_method
: diag::err_coroutine_outside_function) << Keyword;
} else if (isa<CXXConstructorDecl>(FD) || isa<CXXDestructorDecl>(FD)) {
S.Diag(Loc, diag::err_coroutine_ctor_dtor)
<< isa<CXXDestructorDecl>(FD) << Keyword;
} else if (FD->isConstexpr()) {
S.Diag(Loc, diag::err_coroutine_constexpr) << Keyword;
} else if (FD->isVariadic()) {
S.Diag(Loc, diag::err_coroutine_varargs) << Keyword;
} else {
auto *ScopeInfo = S.getCurFunction();
assert(ScopeInfo && "missing function scope for function");
if (!ScopeInfo->CoroutinePromise) {
QualType T =
FD->getType()->isDependentType()
? S.Context.DependentTy
: lookupPromiseType(S, FD->getType()->castAs<FunctionProtoType>(),
Loc);
if (T.isNull())
return nullptr;
ScopeInfo->CoroutinePromise =
VarDecl::Create(S.Context, FD, FD->getLocation(), FD->getLocation(),
&S.PP.getIdentifierTable().get("__promise"), T,
S.Context.getTrivialTypeSourceInfo(T, Loc), SC_None);
S.CheckVariableDeclarationType(ScopeInfo->CoroutinePromise);
if (!ScopeInfo->CoroutinePromise->isInvalidDecl())
S.ActOnUninitializedDecl(ScopeInfo->CoroutinePromise, false);
}
return ScopeInfo;
}
return nullptr;
}
static ExprResult buildOperatorCoawaitCall(Sema &SemaRef, Scope *S,
SourceLocation Loc, Expr *E) {
UnresolvedSet<16> Functions;
SemaRef.LookupOverloadedOperatorName(OO_Coawait, S, E->getType(), QualType(),
Functions);
return SemaRef.CreateOverloadedUnaryOp(Loc, UO_Coawait, Functions, E);
}
struct ReadySuspendResumeResult {
bool IsInvalid;
Expr *Results[3];
};
static ExprResult buildMemberCall(Sema &S, Expr *Base, SourceLocation Loc,
StringRef Name,
MutableArrayRef<Expr *> Args) {
DeclarationNameInfo NameInfo(&S.PP.getIdentifierTable().get(Name), Loc);
CXXScopeSpec SS;
ExprResult Result = S.BuildMemberReferenceExpr(
Base, Base->getType(), Loc, false, SS,
SourceLocation(), nullptr, NameInfo, nullptr,
nullptr);
if (Result.isInvalid())
return ExprError();
return S.ActOnCallExpr(nullptr, Result.get(), Loc, Args, Loc, nullptr);
}
static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, SourceLocation Loc,
Expr *E) {
ReadySuspendResumeResult Calls = {true, {}};
const StringRef Funcs[] = {"await_ready", "await_suspend", "await_resume"};
for (size_t I = 0, N = llvm::array_lengthof(Funcs); I != N; ++I) {
Expr *Operand = new (S.Context) OpaqueValueExpr(
Loc, E->getType(), VK_LValue, E->getObjectKind(), E);
ExprResult Result = buildMemberCall(S, Operand, Loc, Funcs[I], None);
if (Result.isInvalid())
return Calls;
Calls.Results[I] = Result.get();
}
Calls.IsInvalid = false;
return Calls;
}
ExprResult Sema::ActOnCoawaitExpr(Scope *S, SourceLocation Loc, Expr *E) {
if (E->getType()->isPlaceholderType()) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return ExprError();
E = R.get();
}
ExprResult Awaitable = buildOperatorCoawaitCall(*this, S, Loc, E);
if (Awaitable.isInvalid())
return ExprError();
return BuildCoawaitExpr(Loc, Awaitable.get());
}
ExprResult Sema::BuildCoawaitExpr(SourceLocation Loc, Expr *E) {
auto *Coroutine = checkCoroutineContext(*this, Loc, "co_await");
if (!Coroutine)
return ExprError();
if (E->getType()->isPlaceholderType()) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return ExprError();
E = R.get();
}
if (E->getType()->isDependentType()) {
Expr *Res = new (Context) CoawaitExpr(Loc, Context.DependentTy, E);
Coroutine->CoroutineStmts.push_back(Res);
return Res;
}
if (E->getValueKind() == VK_RValue)
E = new (Context) MaterializeTemporaryExpr(E->getType(), E, true);
ReadySuspendResumeResult RSS = buildCoawaitCalls(*this, Loc, E);
if (RSS.IsInvalid)
return ExprError();
Expr *Res = new (Context) CoawaitExpr(Loc, E, RSS.Results[0], RSS.Results[1],
RSS.Results[2]);
Coroutine->CoroutineStmts.push_back(Res);
return Res;
}
static ExprResult buildPromiseCall(Sema &S, FunctionScopeInfo *Coroutine,
SourceLocation Loc, StringRef Name,
MutableArrayRef<Expr *> Args) {
assert(Coroutine->CoroutinePromise && "no promise for coroutine");
auto *Promise = Coroutine->CoroutinePromise;
ExprResult PromiseRef = S.BuildDeclRefExpr(
Promise, Promise->getType().getNonReferenceType(), VK_LValue, Loc);
if (PromiseRef.isInvalid())
return ExprError();
return buildMemberCall(S, PromiseRef.get(), Loc, Name, Args);
}
ExprResult Sema::ActOnCoyieldExpr(Scope *S, SourceLocation Loc, Expr *E) {
auto *Coroutine = checkCoroutineContext(*this, Loc, "co_yield");
if (!Coroutine)
return ExprError();
ExprResult Awaitable =
buildPromiseCall(*this, Coroutine, Loc, "yield_value", E);
if (Awaitable.isInvalid())
return ExprError();
Awaitable = buildOperatorCoawaitCall(*this, S, Loc, Awaitable.get());
if (Awaitable.isInvalid())
return ExprError();
return BuildCoyieldExpr(Loc, Awaitable.get());
}
ExprResult Sema::BuildCoyieldExpr(SourceLocation Loc, Expr *E) {
auto *Coroutine = checkCoroutineContext(*this, Loc, "co_yield");
if (!Coroutine)
return ExprError();
if (E->getType()->isPlaceholderType()) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return ExprError();
E = R.get();
}
if (E->getType()->isDependentType()) {
Expr *Res = new (Context) CoyieldExpr(Loc, Context.DependentTy, E);
Coroutine->CoroutineStmts.push_back(Res);
return Res;
}
if (E->getValueKind() == VK_RValue)
E = new (Context) MaterializeTemporaryExpr(E->getType(), E, true);
ReadySuspendResumeResult RSS = buildCoawaitCalls(*this, Loc, E);
if (RSS.IsInvalid)
return ExprError();
Expr *Res = new (Context) CoyieldExpr(Loc, E, RSS.Results[0], RSS.Results[1],
RSS.Results[2]);
Coroutine->CoroutineStmts.push_back(Res);
return Res;
}
StmtResult Sema::ActOnCoreturnStmt(SourceLocation Loc, Expr *E) {
return BuildCoreturnStmt(Loc, E);
}
StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E) {
auto *Coroutine = checkCoroutineContext(*this, Loc, "co_return");
if (!Coroutine)
return StmtError();
if (E && E->getType()->isPlaceholderType() &&
!E->getType()->isSpecificPlaceholderType(BuiltinType::Overload)) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return StmtError();
E = R.get();
}
ExprResult PC;
if (E && !E->getType()->isVoidType()) {
PC = buildPromiseCall(*this, Coroutine, Loc, "return_value", E);
} else {
E = MakeFullDiscardedValueExpr(E).get();
PC = buildPromiseCall(*this, Coroutine, Loc, "return_void", None);
}
if (PC.isInvalid())
return StmtError();
Expr *PCE = ActOnFinishFullExpr(PC.get()).get();
Stmt *Res = new (Context) CoreturnStmt(Loc, E, PCE);
Coroutine->CoroutineStmts.push_back(Res);
return Res;
}
void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
FunctionScopeInfo *Fn = getCurFunction();
assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine");
if (Fn->FirstReturnLoc.isValid()) {
Diag(Fn->FirstReturnLoc, diag::err_return_in_coroutine);
auto *First = Fn->CoroutineStmts[0];
Diag(First->getLocStart(), diag::note_declared_coroutine_here)
<< (isa<CoawaitExpr>(First) ? 0 :
isa<CoyieldExpr>(First) ? 1 : 2);
}
bool AnyCoawaits = false;
bool AnyCoyields = false;
for (auto *CoroutineStmt : Fn->CoroutineStmts) {
AnyCoawaits |= isa<CoawaitExpr>(CoroutineStmt);
AnyCoyields |= isa<CoyieldExpr>(CoroutineStmt);
}
if (!AnyCoawaits && !AnyCoyields)
Diag(Fn->CoroutineStmts.front()->getLocStart(),
diag::ext_coroutine_without_co_await_co_yield);
SourceLocation Loc = FD->getLocation();
StmtResult PromiseStmt =
ActOnDeclStmt(ConvertDeclToDeclGroup(Fn->CoroutinePromise), Loc, Loc);
if (PromiseStmt.isInvalid())
return FD->setInvalidDecl();
ExprResult InitialSuspend =
buildPromiseCall(*this, Fn, Loc, "initial_suspend", None);
if (!InitialSuspend.isInvalid())
InitialSuspend = BuildCoawaitExpr(Loc, InitialSuspend.get());
InitialSuspend = ActOnFinishFullExpr(InitialSuspend.get());
if (InitialSuspend.isInvalid())
return FD->setInvalidDecl();
ExprResult FinalSuspend =
buildPromiseCall(*this, Fn, Loc, "final_suspend", None);
if (!FinalSuspend.isInvalid())
FinalSuspend = BuildCoawaitExpr(Loc, FinalSuspend.get());
FinalSuspend = ActOnFinishFullExpr(FinalSuspend.get());
if (FinalSuspend.isInvalid())
return FD->setInvalidDecl();
ExprResult ReturnObject =
buildPromiseCall(*this, Fn, Loc, "get_return_object", None);
if (ReturnObject.isInvalid())
return FD->setInvalidDecl();
QualType RetType = FD->getReturnType();
if (!RetType->isDependentType()) {
InitializedEntity Entity =
InitializedEntity::InitializeResult(Loc, RetType, false);
ReturnObject = PerformMoveOrCopyInitialization(Entity, nullptr, RetType,
ReturnObject.get());
if (ReturnObject.isInvalid())
return FD->setInvalidDecl();
}
ReturnObject = ActOnFinishFullExpr(ReturnObject.get(), Loc);
if (ReturnObject.isInvalid())
return FD->setInvalidDecl();
SmallVector<Expr*, 16> ParamMoves;
Body = new (Context) CoroutineBodyStmt(
Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(),
nullptr, nullptr,
ReturnObject.get(), ParamMoves);
}