aaronpuchert updated this revision to Diff 226980.
aaronpuchert marked an inline comment as done.
aaronpuchert added a comment.

Add test case where check for non-deduced parameter conversions is necessary.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D68845/new/

https://reviews.llvm.org/D68845

Files:
  clang/lib/Sema/SemaCoroutine.cpp
  clang/test/SemaCXX/coroutine-rvo.cpp

Index: clang/test/SemaCXX/coroutine-rvo.cpp
===================================================================
--- clang/test/SemaCXX/coroutine-rvo.cpp
+++ clang/test/SemaCXX/coroutine-rvo.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -stdlib=libc++ -std=c++1z -fcoroutines-ts -fsyntax-only
+// RUN: %clang_cc1 -verify -std=c++17 -fcoroutines-ts -fsyntax-only %s
 
 namespace std::experimental {
 template <class Promise = void> struct coroutine_handle {
@@ -39,10 +39,14 @@
 };
 
 struct MoveOnly {
-  MoveOnly() {};
+  MoveOnly() = default;
   MoveOnly(const MoveOnly&) = delete;
-  MoveOnly(MoveOnly&&) noexcept {};
-  ~MoveOnly() {};
+  MoveOnly(MoveOnly &&) = default;
+};
+
+struct NoCopyNoMove {
+  NoCopyNoMove() = default;
+  NoCopyNoMove(const NoCopyNoMove &) = delete;
 };
 
 template <typename T>
@@ -52,18 +56,111 @@
     auto final_suspend() { return suspend_never{}; }
     auto get_return_object() { return task{}; }
     static void unhandled_exception() {}
-    void return_value(T&& value) {}
+    void return_value(T &&value) {} // expected-note 2{{passing argument}}
+  };
+};
+
+task<NoCopyNoMove> local2val() {
+  NoCopyNoMove value;
+  co_return value;
+}
+
+task<NoCopyNoMove &> local2ref() {
+  NoCopyNoMove value;
+  co_return value;
+}
+
+// We need the move constructor for construction of the coroutine.
+task<MoveOnly> param2val(MoveOnly value) {
+  co_return value;
+}
+
+task<NoCopyNoMove> lvalue2val(NoCopyNoMove &value) {
+  co_return value; // expected-error{{rvalue reference to type 'NoCopyNoMove' cannot bind to lvalue of type 'NoCopyNoMove'}}
+}
+
+task<NoCopyNoMove> rvalue2val(NoCopyNoMove &&value) {
+  co_return value;
+}
+
+task<NoCopyNoMove &> lvalue2ref(NoCopyNoMove &value) {
+  co_return value;
+}
+
+task<NoCopyNoMove &> rvalue2ref(NoCopyNoMove &&value) {
+  co_return value;
+}
+
+struct To {
+  operator MoveOnly() &&;
+};
+task<MoveOnly> conversion_operator() {
+  To t;
+  co_return t;
+}
+
+struct Construct {
+  Construct(MoveOnly);
+};
+task<Construct> converting_constructor() {
+  MoveOnly w;
+  co_return w;
+}
+
+struct Derived : MoveOnly {};
+task<MoveOnly> derived2base() {
+  Derived result;
+  co_return result;
+}
+
+struct RetThis {
+  task<RetThis> foo() && {
+    co_return *this; // expected-error{{rvalue reference to type 'RetThis' cannot bind to lvalue of type 'RetThis'}}
+  }
+};
+
+template <typename, typename>
+struct is_same { static constexpr bool value = false; };
+template <typename T>
+struct is_same<T, T> { static constexpr bool value = true; };
+
+template <bool>
+struct enable_if;
+template <>
+struct enable_if<false> { };
+template <>
+struct enable_if<true> { struct type; };
+
+struct MoveOnly2 : MoveOnly {};
+
+template <typename T>
+struct template_return_task {
+  struct promise_type {
+    auto initial_suspend() { return suspend_never{}; }
+    auto final_suspend() { return suspend_never{}; }
+    auto get_return_object() { return template_return_task{}; }
+    static void unhandled_exception();
+    template <typename U, typename = typename enable_if<!is_same<U, MoveOnly2>::value>::type>
+    void return_value(U &&value) {
+      static_assert(is_same<T, U>::value);
+    }
+    template <typename U = void>
+    void return_value(int value) {}
   };
 };
 
-task<MoveOnly> f() {
-  MoveOnly value;
+// We should deduce U = MoveOnly via implicit move.
+template_return_task<MoveOnly> param2template(MoveOnly value) {
   co_return value;
 }
 
-int main() {
-  f();
-  return 0;
+// We should deduce U = NoCopyNoMove&, because we can't implicitly move.
+template_return_task<NoCopyNoMove &> lvalue2template(NoCopyNoMove &value) {
+  co_return value;
 }
 
-// expected-no-diagnostics
+// We should deduce U = MoveOnly2&, because one overload is a substitution
+// failure and for the other implicit conversions fail.
+template_return_task<MoveOnly2&> param2template(MoveOnly2 value) {
+  co_return value;
+}
Index: clang/lib/Sema/SemaCoroutine.cpp
===================================================================
--- clang/lib/Sema/SemaCoroutine.cpp
+++ clang/lib/Sema/SemaCoroutine.cpp
@@ -851,6 +851,72 @@
   return BuildCoreturnStmt(Loc, E);
 }
 
+// [class.copy.elision]p3: If the expression in a [...] co_­return statement
+// is a (possibly parenthesized) id-expression that names an implicitly
+// movable entity declared in the body or parameter-declaration-clause of
+// the innermost enclosing function or lambda-expression, [...] overload
+// resolution to select [...] the return_­value overload to call is first
+// performed as if the expression or operand were an rvalue. If the first
+// overload resolution fails or was not performed, overload resolution is
+// performed again, considering the expression or operand as an lvalue.
+static bool shouldMoveReturnExpr(Sema &S, RecordDecl *PromiseTypeDecl,
+                                 Expr *E) {
+  // Check if we're allowed to implicitly move.
+  VarDecl *ImplicitMoveCandidate =
+      S.getCopyElisionCandidate(E->getType(), E, Sema::CES_Default);
+  if (!ImplicitMoveCandidate)
+    return false;
+  if (ImplicitMoveCandidate->getType()->isLValueReferenceType())
+    return false;
+
+  ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, E->getType(), CK_NoOp, E,
+                            VK_XValue);
+
+  // Lookup return_value methods in the promise type, and check if any accepts
+  // the moved expression. If it does, we do the move.
+  DeclarationNameInfo NameInfo(&S.PP.getIdentifierTable().get("return_value"),
+                               SourceLocation{});
+  LookupResult LR(S, NameInfo, Sema::LookupMemberName);
+  CXXScopeSpec SS;
+  S.LookupQualifiedName(LR, PromiseTypeDecl, SS);
+  for (NamedDecl *D : LR) {
+    if (const auto *USD = dyn_cast<UsingShadowDecl>(D))
+      D = USD->getTargetDecl();
+    if (const auto *CMD = dyn_cast<CXXMethodDecl>(D)) {
+      if (CMD->isDeleted())
+        continue;
+      if (CMD->getNumParams() < 1)
+        continue;
+      for (unsigned param = 1; param < CMD->getNumParams(); ++param)
+        if (!CMD->getParamDecl(param)->hasDefaultArg())
+          continue;
+      InitializedEntity Entity = InitializedEntity::InitializeParameter(
+          S.Context, CMD->getParamDecl(0));
+      if (S.CanPerformCopyInitialization(Entity, &AsRvalue))
+        return true;
+    } else if (auto *FTD = dyn_cast<FunctionTemplateDecl>(D)) {
+      TemplateDeductionInfo Info(SourceLocation{});
+      FunctionDecl *Specialization = nullptr;
+      Sema::TemplateDeductionResult Result = S.DeduceTemplateArguments(
+          FTD, {}, {&AsRvalue}, Specialization, Info, false,
+          [&](ArrayRef<QualType> ParamTypes) {
+            QualType ParamType = ParamTypes[0];
+            if (!ParamType->isDependentType()) {
+              InitializedEntity Entity = InitializedEntity::InitializeParameter(
+                  S.Context, ParamType, false);
+              if (!S.CanPerformCopyInitialization(Entity, &AsRvalue))
+                return true;
+            }
+            return false;
+          });
+      if (Result == Sema::TDK_Success)
+        return true;
+    }
+    // TODO: handle VarDecls. They can be function pointers or function objects.
+  }
+  return false;
+}
+
 StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E,
                                    bool IsImplicit) {
   auto *FSI = checkCoroutineContext(*this, Loc, "co_return", IsImplicit);
@@ -864,26 +930,15 @@
     E = R.get();
   }
 
-  // Move the return value if we can
-  if (E) {
-    auto NRVOCandidate = this->getCopyElisionCandidate(E->getType(), E, CES_AsIfByStdMove);
-    if (NRVOCandidate) {
-      InitializedEntity Entity =
-          InitializedEntity::InitializeResult(Loc, E->getType(), NRVOCandidate);
-      ExprResult MoveResult = this->PerformMoveOrCopyInitialization(
-          Entity, NRVOCandidate, E->getType(), E);
-      if (MoveResult.get())
-        E = MoveResult.get();
-    }
-  }
-
-  // FIXME: If the operand is a reference to a variable that's about to go out
-  // of scope, we should treat the operand as an xvalue for this overload
-  // resolution.
   VarDecl *Promise = FSI->CoroutinePromise;
   ExprResult PC;
   if (E && (isa<InitListExpr>(E) || !E->getType()->isVoidType())) {
-    PC = buildPromiseCall(*this, Promise, Loc, "return_value", E);
+    Expr *Arg = E;
+    if (const RecordType *PromiseT = Promise->getType()->getAs<RecordType>())
+      if (shouldMoveReturnExpr(*this, PromiseT->getDecl(), E))
+        Arg = ImplicitCastExpr::Create(Context, E->getType(), CK_NoOp, E,
+                                       nullptr, VK_XValue);
+    PC = buildPromiseCall(*this, Promise, Loc, "return_value", Arg);
   } else {
     E = MakeFullDiscardedValueExpr(E).get();
     PC = buildPromiseCall(*this, Promise, Loc, "return_void", None);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to