================
@@ -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

Reply via email to