================ @@ -401,36 +432,85 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise, return Calls; } Expr *CoroHandle = CoroHandleRes.get(); + Calls.UseAwaitSuspendDestroy = false; CallExpr *AwaitSuspend = cast_or_null<CallExpr>( BuildSubExpr(ACT::ACT_Suspend, "await_suspend", CoroHandle)); if (!AwaitSuspend) return Calls; + + // When this `await_suspend()` overload is annotated with + // `[[clang::coro_await_suspend_destroy]]`, do NOT call `await_suspend()` -- + // instead call `await_suspend_destroy(Promise&)`. This assumes that the + // `await_suspend()` is just a compatibility stub consisting of: + // await_suspend_destroy(handle.promise()); + // handle.destroy(); + // Users of the attribute must follow this contract. Then, diagnostics from + // both `await_suspend` and `await_suspend_destroy` will get exposed. + CallExpr *PlainAwaitSuspend = nullptr; + if (FunctionDecl *AwaitSuspendCallee = AwaitSuspend->getDirectCallee()) { + if (AwaitSuspendCallee->hasAttr<CoroAwaitSuspendDestroyAttr>()) { + Calls.UseAwaitSuspendDestroy = true; + ExprResult PromiseRefRes = + buildPromiseRef(S, CoroPromise->getType(), Loc); + if (PromiseRefRes.isInvalid()) { + Calls.IsInvalid = true; + return Calls; + } + Expr *PromiseRef = PromiseRefRes.get(); + PlainAwaitSuspend = AwaitSuspend; + AwaitSuspend = cast_or_null<CallExpr>( + BuildSubExpr(ACT::ACT_Suspend, "await_suspend_destroy", PromiseRef)); + if (!AwaitSuspend) + return Calls; + } + } ---------------- snarkmaster wrote:
> why do overload resolution At a high level, because the behavior of the awaiter can depend on the coroutine type it's suspending. Very concretely, with `folly::result`, I would like `co_await or_unwind(someResultFn())` to unwrap the result , or propagate the error, in **both** `folly::result` coroutines (short-circuiting / non-suspending) and `folly::coro::Task` coroutines (asynchronous, sometimes-susspending). So, the above `or_unwind` awaiter can only apply `[[clang::coro_await_suspend_destroy]]` to `await_suspend(std::coroutine_handle<folly::result_promise<T>>)`, but not to the `Task` promise type. In the current version of this PR, the main `lit` IR test does cover this promise-dependent overload behavior. So, you can look at that for minimal example code. --- > good method to resolve overloads I hacked something up using `LookupQualifiedName` and `OverloadCandidateSet::AddMethodCandidate`. Corner case: this calls `DeduceReturnType` when `await_suspend` both has the attribute & returns `auto`. I have to run body analysis in order to issue the "return types don't match" diagnostic. If that's too expensive, then 1) It would not be a huge problem to drop the `DeduceReturnType` call and just make it an error, forcing the user to make the return type known from the declaration. 2) The "return types must match" diagnostic is not THAT high-value, so we could even drop the diagnostic, and let users who don't read the docs shoot themselves in the foot. Does the following lookup strategy seem like an improvement to you? If yes, I'll use it to only create the one call expression we actually need, and add some more tests for the diagnostic. ```cpp /// Return the type of `await_suspend` if it has `CoroAwaitSuspendDestroyAttr`. /// /// @returns A null type on failure to look up the `await_suspend` overload. /// @returns A null type the `await_suspend` overload lacks the attribute. /// @returns The return type of the selected `await_suspend` overload if it has /// the attribute, which is also the expected return type of the /// `await_suspend_destroy` method that will be called instead. static QualType lookupReturnTypeIfAwaitSuspendDestroy(Sema &S, Expr *Operand, QualType ArgumentType, SourceLocation Loc) { QualType ObjectType = Operand->getType(); LookupResult Found(S, S.PP.getIdentifierInfo("await_suspend"), Loc, Sema::LookupOrdinaryName); { CXXRecordDecl *ObjectRD = ObjectType->getAsCXXRecordDecl(); if (!ObjectRD) return QualType(); S.LookupQualifiedName(Found, ObjectRD); if (Found.empty()) return QualType(); } // Attempt overload resolution using a dummy argument OverloadCandidateSet Candidates(Loc, OverloadCandidateSet::CSK_Normal); OpaqueValueExpr HandleArg(Loc, ArgumentType, VK_PRValue); SmallVector<Expr *, 1> Args = {&HandleArg}; for (LookupResult::iterator I = Found.begin(), E = Found.end(); I != E; ++I) { S.AddMethodCandidate(I.getPair(), ObjectType, Operand->Classify(S.Context), Args, Candidates); } OverloadCandidateSet::iterator Best; if (Candidates.BestViableFunction(S, Loc, Best) == OR_Success) { // Test the best candidate for the attribute if (Best->Function && Best->Function->hasAttr<CoroAwaitSuspendDestroyAttr>()) { QualType RetType = Best->Function->getReturnType(); // If the return type is undeduced, try to deduce it from an available // function body or template pattern. If we fail to deduce the type, the // user will see a diagnostic about a mismatch between the return type of // `await_suspend` and `await_suspend_destroy`. if (RetType->isUndeducedType() && // Returns `StillUndeduced == false` if deduction succeeded. !S.DeduceReturnType(Best->Function, Loc, /*Diagnose=*/false)) { return Best->Function->getReturnType(); } return RetType; } } return QualType(); } ``` https://github.com/llvm/llvm-project/pull/152623 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits