[clang] [clang] Add `clang::behaves_like_std(...)` attribute (PR #76596)

Max Winkler via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 29 21:34:42 PST 2023


https://github.com/MaxEW707 updated https://github.com/llvm/llvm-project/pull/76596

>From ff2c4b9310950c56a4e2a2b7752f3c3c442a6805 Mon Sep 17 00:00:00 2001
From: MaxEW707 <82551778+MaxEW707 at users.noreply.github.com>
Date: Fri, 29 Dec 2023 04:02:10 -0500
Subject: [PATCH 1/3] Add `clang::behaves_like_std(...)` attribute

---
 clang/include/clang/Basic/Attr.td             |  9 +++
 clang/include/clang/Basic/AttrDocs.td         | 29 ++++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/lib/Sema/SemaDecl.cpp                   | 47 +++++++++---
 clang/lib/Sema/SemaDeclAttr.cpp               | 16 +++++
 clang/lib/Sema/SemaExpr.cpp                   | 21 ++++++
 clang/test/CodeGenCXX/builtin-std-move.cpp    | 72 +++++++++++++++++++
 clang/test/CodeGenCXX/builtins.cpp            |  7 ++
 .../SemaCXX/err-invalid-behaves-like-std.cpp  | 20 ++++++
 .../SemaCXX/unqualified-std-call-fixits.cpp   | 28 +++++++-
 clang/test/SemaCXX/warn-pessmizing-move.cpp   | 22 ++++++
 clang/test/SemaCXX/warn-redundant-move.cpp    | 47 ++++++++++--
 clang/test/SemaCXX/warn-self-move.cpp         | 55 ++++++++++++++
 13 files changed, 359 insertions(+), 16 deletions(-)
 create mode 100644 clang/test/SemaCXX/err-invalid-behaves-like-std.cpp

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index db17211747b17d..5ad72c7026425d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1922,6 +1922,15 @@ def Convergent : InheritableAttr {
   let SimpleHandler = 1;
 }
 
+def BehavesLikeStd : InheritableAttr {
+  let Spellings = [Clang<"behaves_like_std">];
+  let Subjects = SubjectList<[Function]>;
+  let Args = [StringArgument<"StdFunction">];
+  let LangOpts = [CPlusPlus];
+  let PragmaAttributeSupport = 0;
+  let Documentation = [BehavesLikeStdDocs];
+}
+
 def NoInline : DeclOrStmtAttr {
   let Spellings = [CustomKeyword<"__noinline__">, GCC<"noinline">,
                    CXX11<"clang", "noinline">, C23<"clang", "noinline">,
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 98a7ecc7fd7df3..5d99bb87587ceb 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -577,6 +577,35 @@ effect applies only to a specific function pointer. For example:
   }];
 }
 
+def BehavesLikeStdDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+This function attribute can be used to tag functions that behave like `std` functions.
+This allows custom STL libraries in non-freestanding environments to get the same benefits
+as the `std` functions that are treated like builtins without conflicting with the `std` declarations
+and without including costly `std` headers.
+
+This attribute currently supports all `std` functions that are implicitly treated as builtins which include
+`std::addressof`, `std::forward`, `std::forward_like`, `std::move`, `std::move_if_noexcept`, and `std::as_const`.
+
+.. code-block:: c
+
+  namespace MySTL {
+    template<class T>
+    [[clang::behaves_like_std("move")]] constexpr remove_reference_t<T>&& move(T &&t) noexcept;
+  }
+
+  template<class T>
+  [[clang::behaves_like_std("move")]] constexpr remove_reference_t<T>&& myMove(T &&t) noexcept;
+
+  void example(std::string a, std::string b) {
+    foo(MySTL::move(a));
+    foo(myMove(b));
+  }
+
+  }];
+}
+
 def NoInlineDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index aebb7d9b945c33..4ebcda089ca307 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3132,6 +3132,8 @@ def err_attribute_no_member_function : Error<
 def err_attribute_parameter_types : Error<
   "%0 attribute parameter types do not match: parameter %1 of function %2 has type %3, "
   "but parameter %4 of function %5 has type %6">;
+def err_attribute_invalid_std_builtin : Error<
+  "not a valid std builtin for attribute %0">;
 
 def err_attribute_too_many_arguments : Error<
   "%0 attribute takes no more than %1 argument%s1">;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index ffbe317d559995..2498af5c7e6a1a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -9753,14 +9753,11 @@ static Scope *getTagInjectionScope(Scope *S, const LangOptions &LangOpts) {
   return S;
 }
 
-/// Determine whether a declaration matches a known function in namespace std.
-static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD,
-                         unsigned BuiltinID) {
+/// Determine whether a declaration matches a known cast function in namespace
+/// std.
+static bool isStdCastBuiltin(ASTContext &Ctx, FunctionDecl *FD,
+                             unsigned BuiltinID) {
   switch (BuiltinID) {
-  case Builtin::BI__GetExceptionInfo:
-    // No type checking whatsoever.
-    return Ctx.getTargetInfo().getCXXABI().isMicrosoft();
-
   case Builtin::BIaddressof:
   case Builtin::BI__addressof:
   case Builtin::BIforward:
@@ -9774,12 +9771,23 @@ static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD,
     const auto *FPT = FD->getType()->castAs<FunctionProtoType>();
     return FPT->getNumParams() == 1 && !FPT->isVariadic();
   }
-
   default:
     return false;
   }
 }
 
+/// Determine whether a declaration matches a known function in namespace std.
+static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD,
+                         unsigned BuiltinID) {
+  switch (BuiltinID) {
+  case Builtin::BI__GetExceptionInfo:
+    // No type checking whatsoever.
+    return Ctx.getTargetInfo().getCXXABI().isMicrosoft();
+  default:
+    return isStdCastBuiltin(Ctx, FD, BuiltinID);
+  }
+}
+
 NamedDecl*
 Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
                               TypeSourceInfo *TInfo, LookupResult &Previous,
@@ -10704,11 +10712,28 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   // If this is the first declaration of a library builtin function, add
   // attributes as appropriate.
   if (!D.isRedeclaration()) {
-    if (IdentifierInfo *II = Previous.getLookupName().getAsIdentifierInfo()) {
+    IdentifierInfo *II = nullptr;
+    if (auto *GA = NewFD->getAttr<BehavesLikeStdAttr>()) {
+      auto iter = PP.getIdentifierTable().find(GA->getStdFunction());
+      if (iter != PP.getIdentifierTable().end())
+        II = iter->getValue();
+      else
+        Diag(NewFD->getLocation(), diag::err_attribute_invalid_std_builtin)
+            << GA;
+    } else {
+      II = Previous.getLookupName().getAsIdentifierInfo();
+    }
+    if (II) {
       if (unsigned BuiltinID = II->getBuiltinID()) {
         bool InStdNamespace = Context.BuiltinInfo.isInStdNamespace(BuiltinID);
-        if (!InStdNamespace &&
-            NewFD->getDeclContext()->getRedeclContext()->isFileContext()) {
+        if (NewFD->hasAttr<BehavesLikeStdAttr>()) {
+          if (!InStdNamespace || !isStdCastBuiltin(Context, NewFD, BuiltinID))
+            Diag(NewFD->getLocation(), diag::err_attribute_invalid_std_builtin)
+                << NewFD->getAttr<BehavesLikeStdAttr>();
+          NewFD->addAttr(BuiltinAttr::Create(Context, BuiltinID));
+        } else if (!InStdNamespace && NewFD->getDeclContext()
+                                          ->getRedeclContext()
+                                          ->isFileContext()) {
           if (NewFD->getLanguageLinkage() == CLanguageLinkage) {
             // Validate the type matches unless this builtin is specified as
             // matching regardless of its declared type.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index af8b90ecfed973..a48e0c19fce23a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8485,6 +8485,19 @@ static void handleNoUniqueAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(NoUniqueAddressAttr::Create(S.Context, AL));
 }
 
+static void handleBehavesLikeStdAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  if (AL.getNumArgs() > 1) {
+    S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
+    return;
+  }
+
+  StringRef Str;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+    return;
+
+  D->addAttr(BehavesLikeStdAttr::Create(S.Context, Str, AL));
+}
+
 static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   // The 'sycl_kernel' attribute applies only to function templates.
   const auto *FD = cast<FunctionDecl>(D);
@@ -9397,6 +9410,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_NoUniqueAddress:
     handleNoUniqueAddressAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_BehavesLikeStd:
+    handleBehavesLikeStdAttr(S, D, AL);
+    break;
 
   case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 960f513d1111b2..4adac2e61fdc52 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7145,6 +7145,27 @@ static void DiagnosedUnqualifiedCallsToStdFunctions(Sema &S,
   if (BuiltinID != Builtin::BImove && BuiltinID != Builtin::BIforward)
     return;
 
+  if (auto *GA = FD->getAttr<BehavesLikeStdAttr>()) {
+    if (auto *DC = FD->getDeclContext()) {
+      const NamespaceDecl *NSD = nullptr;
+      while (DC->isNamespace()) {
+        NSD = cast<NamespaceDecl>(DC);
+        if (NSD->isInline())
+          DC = NSD->getParent();
+        else
+          break;
+      }
+      if (NSD && NSD->getIdentifier()) {
+        std::string Name = NSD->getIdentifier()->getName().str() + "::";
+        S.Diag(DRE->getLocation(),
+               diag::warn_unqualified_call_to_std_cast_function)
+            << FD->getQualifiedNameAsString()
+            << FixItHint::CreateInsertion(DRE->getLocation(), Name);
+      }
+    }
+    return;
+  }
+
   S.Diag(DRE->getLocation(), diag::warn_unqualified_call_to_std_cast_function)
       << FD->getQualifiedNameAsString()
       << FixItHint::CreateInsertion(DRE->getLocation(), "std::");
diff --git a/clang/test/CodeGenCXX/builtin-std-move.cpp b/clang/test/CodeGenCXX/builtin-std-move.cpp
index 6eb7073d67f1cc..91f9c3d06f04c6 100644
--- a/clang/test/CodeGenCXX/builtin-std-move.cpp
+++ b/clang/test/CodeGenCXX/builtin-std-move.cpp
@@ -11,6 +11,20 @@ namespace std {
   template<typename T, typename U> T move(U source, U source_end, T dest);
 }
 
+namespace mystd {
+  template<typename T> [[clang::behaves_like_std("move")]] constexpr T &&move(T &val) { return static_cast<T&&>(val); }
+  template<typename T> [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&move_if_noexcept(T &val);
+  template<typename T> [[clang::behaves_like_std("forward")]] constexpr T &&forward(T &val);
+  template<typename U, typename T> [[clang::behaves_like_std("forward_like")]] constexpr T &&forward_like(T &&val);
+  template<typename T> [[clang::behaves_like_std("as_const")]] constexpr const T &as_const(T &val);
+}
+
+template<typename T> [[clang::behaves_like_std("move")]] constexpr T &&mymove(T &val) { return static_cast<T&&>(val); }
+template<typename T> [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&mymove_if_noexcept(T &val);
+template<typename T> [[clang::behaves_like_std("forward")]] constexpr T &&myforward(T &val);
+template<typename U, typename T> [[clang::behaves_like_std("forward_like")]] constexpr T &&myforward_like(T &&val);
+template<typename T> [[clang::behaves_like_std("as_const")]] constexpr const T &myas_const(T &val);
+
 class T {};
 extern "C" void take(T &&);
 extern "C" void take_lval(const T &);
@@ -27,6 +41,24 @@ T &forward_a = std::forward<T&>(a);
 // CHECK-DAG: @forward_like_a = constant ptr @a
 T &forward_like_a = std::forward_like<int&>(a);
 
+// CHECK-DAG: @move_a_2 = constant ptr @a
+T &&move_a_2 = mystd::move(a);
+// CHECK-DAG: @move_if_noexcept_a_2 = constant ptr @a
+T &&move_if_noexcept_a_2 = mystd::move_if_noexcept(a);
+// CHECK-DAG: @forward_a_2 = constant ptr @a
+T &forward_a_2 = mystd::forward<T&>(a);
+// CHECK-DAG: @forward_like_a_2 = constant ptr @a
+T &forward_like_a_2 = mystd::forward_like<int&>(a);
+
+// CHECK-DAG: @move_a_3 = constant ptr @a
+T &&move_a_3 = mymove(a);
+// CHECK-DAG: @move_if_noexcept_a_3 = constant ptr @a
+T &&move_if_noexcept_a_3 = mymove_if_noexcept(a);
+// CHECK-DAG: @forward_a_3 = constant ptr @a
+T &forward_a_3 = myforward<T&>(a);
+// CHECK-DAG: @forward_like_a_3 = constant ptr @a
+T &forward_like_a_3 = myforward_like<int&>(a);
+
 // Check emission of a non-constant call.
 // CHECK-LABEL: define {{.*}} void @test
 extern "C" void test(T &t) {
@@ -53,6 +85,46 @@ extern "C" void test(T &t) {
 
 // CHECK: declare {{.*}} @_ZSt4moveI1TS0_ET_T0_S2_S1_
 
+// CHECK-LABEL: define {{.*}} void @test2
+extern "C" void test2(T &t) {
+  // CHECK: store ptr %{{.*}}, ptr %[[T_REF:[^,]*]]
+  // CHECK: %0 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %0)
+  take(mystd::move(t));
+  // CHECK: %1 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %1)
+  take(mystd::move_if_noexcept(t));
+  // CHECK: %2 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %2)
+  take(mystd::forward<T&&>(t));
+  // CHECK: %3 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take_lval(ptr {{.*}} %3)
+  take_lval(mystd::forward_like<int&>(t));
+  // CHECK: %4 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take_lval(ptr {{.*}} %4)
+  take_lval(mystd::as_const<T&&>(t));
+}
+
+// CHECK-LABEL: define {{.*}} void @test3
+extern "C" void test3(T &t) {
+  // CHECK: store ptr %{{.*}}, ptr %[[T_REF:[^,]*]]
+  // CHECK: %0 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %0)
+  take(mymove(t));
+  // CHECK: %1 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %1)
+  take(mymove_if_noexcept(t));
+  // CHECK: %2 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take(ptr {{.*}} %2)
+  take(myforward<T&&>(t));
+  // CHECK: %3 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take_lval(ptr {{.*}} %3)
+  take_lval(myforward_like<int&>(t));
+  // CHECK: %4 = load ptr, ptr %[[T_REF]]
+  // CHECK: call void @take_lval(ptr {{.*}} %4)
+  take_lval(myas_const<T&&>(t));
+}
+
 // Check that we instantiate and emit if the address is taken.
 // CHECK-LABEL: define {{.*}} @use_address
 extern "C" void *use_address() {
diff --git a/clang/test/CodeGenCXX/builtins.cpp b/clang/test/CodeGenCXX/builtins.cpp
index 90265186fb3d8c..aea494dcb5a942 100644
--- a/clang/test/CodeGenCXX/builtins.cpp
+++ b/clang/test/CodeGenCXX/builtins.cpp
@@ -31,6 +31,7 @@ S *addressof(bool b, S &s, S &t) {
 }
 
 namespace std { template<typename T> T *addressof(T &); }
+namespace mystd { template<typename T> [[clang::behaves_like_std("addressof")]] T *addressof(T &); }
 
 // CHECK: define {{.*}} @_Z13std_addressofbR1SS0_(
 S *std_addressof(bool b, S &s, S &t) {
@@ -39,6 +40,12 @@ S *std_addressof(bool b, S &s, S &t) {
   return std::addressof(b ? s : t);
 }
 
+S *mystd_addressof(bool b, S &s, S &t) {
+  // CHECK: %[[LVALUE:.*]] = phi
+  // CHECK: ret ptr %[[LVALUE]]
+  return mystd::addressof(b ? s : t);
+}
+
 namespace std { template<typename T> T *__addressof(T &); }
 
 // CHECK: define {{.*}} @_Z15std___addressofbR1SS0_(
diff --git a/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp b/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp
new file mode 100644
index 00000000000000..9374ae9461b045
--- /dev/null
+++ b/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
+
+namespace mystd {
+inline namespace bar {
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T&> { typedef T type; };
+template <class T> struct remove_reference<T&&> { typedef T type; };
+
+template <class T>
+[[clang::behaves_like_std("moved")]] typename remove_reference<T>::type &&move(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}
+
+template <class T>
+[[clang::behaves_like_std("__builtin_abs")]] typename remove_reference<T>::type &&move2(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}
+
+template <class T>
+[[clang::behaves_like_std("strlen")]] typename remove_reference<T>::type &&move3(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}
+
+}
+}
+
diff --git a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
index 1a1ffc7ba2e822..7e48b40ac346fd 100644
--- a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
+++ b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
@@ -12,12 +12,38 @@ int &&forward(auto &a) { return a; }
 
 } // namespace std
 
-using namespace std;
+namespace mystd {
+
+[[clang::behaves_like_std("move")]] int &&move(auto &&a) { return a; }
+
+[[clang::behaves_like_std("forward")]] int &&forward(auto &a) { return a; }
+
+} // namespace mystd
+
+[[clang::behaves_like_std("move")]] int &&mymove(auto &&a) { return a; }
+
+[[clang::behaves_like_std("forward")]] int &&myforward(auto &a) { return a; }
 
 void f() {
+  using namespace std;
   int i = 0;
   (void)move(i); // expected-warning {{unqualified call to 'std::move}}
   // CHECK: {{^}}  (void)std::move
   (void)forward(i); // expected-warning {{unqualified call to 'std::forward}}
   // CHECK: {{^}}  (void)std::forward
 }
+
+void g() {
+  using namespace mystd;
+  int i = 0;
+  (void)move(i); // expected-warning {{unqualified call to 'mystd::move}}
+  // CHECK: {{^}}  (void)mystd::move
+  (void)forward(i); // expected-warning {{unqualified call to 'mystd::forward}}
+  // CHECK: {{^}}  (void)mystd::forward
+}
+
+void h() {
+    int i = 0;
+    (void)mymove(i); // no-warning
+    (void)myforward(i); // no-warning
+}
diff --git a/clang/test/SemaCXX/warn-pessmizing-move.cpp b/clang/test/SemaCXX/warn-pessmizing-move.cpp
index 2c27cd7f95f74d..d9c63dda10d1cd 100644
--- a/clang/test/SemaCXX/warn-pessmizing-move.cpp
+++ b/clang/test/SemaCXX/warn-pessmizing-move.cpp
@@ -13,6 +13,16 @@ template <class T> typename remove_reference<T>::type &&move(T &&t);
 }
 }
 
+namespace mystd {
+inline namespace bar {
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T&> { typedef T type; };
+template <class T> struct remove_reference<T&&> { typedef T type; };
+
+template <class T> [[clang::behaves_like_std("move")]] typename remove_reference<T>::type &&move(T &&t);
+}
+}
+
 struct A {
 #ifdef USER_DEFINED
   A() {}
@@ -39,6 +49,18 @@ A test1(A a1) {
   // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:22-[[@LINE-4]]:23}:""
 }
 
+A test1_mystd(A a1) {
+  A a2;
+  return a1;
+  return a2;
+  return mystd::move(a1);
+  return mystd::move(a2);
+  // expected-warning at -1{{prevents copy elision}}
+  // expected-note at -2{{remove std::move call}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:22}:""
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:24-[[@LINE-4]]:25}:""
+}
+
 B test2(A a1, B b1) {
   // Object is different than return type so don't warn.
   A a2;
diff --git a/clang/test/SemaCXX/warn-redundant-move.cpp b/clang/test/SemaCXX/warn-redundant-move.cpp
index 2bfc8c9312f02e..0617c06702fcd4 100644
--- a/clang/test/SemaCXX/warn-redundant-move.cpp
+++ b/clang/test/SemaCXX/warn-redundant-move.cpp
@@ -13,6 +13,16 @@ template <class T> typename remove_reference<T>::type &&move(T &&t);
 }
 }
 
+namespace mystd {
+inline namespace baz {
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T&> { typedef T type; };
+template <class T> struct remove_reference<T&&> { typedef T type; };
+
+template <class T> [[clang::behaves_like_std("move")]] typename remove_reference<T>::type &&move(T &&t);
+}
+}
+
 // test1 and test2 should not warn until after implementation of DR1579.
 struct A {};
 struct B : public A {};
@@ -86,6 +96,21 @@ D test5(D d) {
   // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:21-[[@LINE-4]]:22}:""
 }
 
+D test6(D d) {
+  return d;
+  // Verify the implicit move from the AST dump
+  // CHECK-AST: ReturnStmt{{.*}}line:[[@LINE-2]]
+  // CHECK-AST-NEXT: CXXConstructExpr{{.*}}D{{.*}}void (D &&)
+  // CHECK-AST-NEXT: ImplicitCastExpr
+  // CHECK-AST-NEXT: DeclRefExpr{{.*}}ParmVar{{.*}}'d'
+
+  return mystd::move(d);
+  // expected-warning at -1{{redundant move in return statement}}
+  // expected-note at -2{{remove std::move call here}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:22}:""
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:23-[[@LINE-4]]:24}:""
+}
+
 namespace templates {
   struct A {};
   struct B { B(A); };
@@ -104,13 +129,27 @@ namespace templates {
     test1<B>(A());
   }
 
+  // Warn once here since the type is not dependent.
+  template <typename T>
+  A test2(A a) {
+    return mystd::move(a);
+    // expected-warning at -1{{redundant move in return statement}}
+    // expected-note at -2{{remove std::move call here}}
+    // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:12-[[@LINE-3]]:24}:""
+    // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:25-[[@LINE-4]]:26}:""
+  }
+  void run_test2() {
+    test2<A>(A());
+    test2<B>(A());
+  }
+
   // T1 and T2 may not be the same, the warning may not always apply.
   template <typename T1, typename T2>
-  T1 test2(T2 t) {
+  T1 test3(T2 t) {
     return std::move(t);
   }
-  void run_test2() {
-    test2<A, A>(A());
-    test2<B, A>(A());
+  void run_test3() {
+    test3<A, A>(A());
+    test3<B, A>(A());
   }
 }
diff --git a/clang/test/SemaCXX/warn-self-move.cpp b/clang/test/SemaCXX/warn-self-move.cpp
index 0987e9b6bf6017..3407820b351bf2 100644
--- a/clang/test/SemaCXX/warn-self-move.cpp
+++ b/clang/test/SemaCXX/warn-self-move.cpp
@@ -11,6 +11,17 @@ template <class T> typename remove_reference<T>::type &&move(T &&t);
 }
 }
 
+namespace mystd {
+inline namespace bar {
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T&> { typedef T type; };
+template <class T> struct remove_reference<T&&> { typedef T type; };
+
+template <class T>
+[[clang::behaves_like_std("move")]] typename remove_reference<T>::type &&move(T &&t);
+}
+}
+
 void int_test() {
   int x = 5;
   x = std::move(x);  // expected-warning{{explicitly moving}}
@@ -21,6 +32,16 @@ void int_test() {
                    expected-warning {{unqualified call to 'std::move}}
 }
 
+void int_test_mystd() {
+    int x = 5;
+    x = mystd::move(x);  // expected-warning{{explicitly moving}}
+    (x) = mystd::move(x);  // expected-warning{{explicitly moving}}
+
+    using mystd::move;
+    x = move(x); // expected-warning{{explicitly moving}} \
+                 expected-warning {{unqualified call to 'mystd::move}}
+}
+
 int global;
 void global_int_test() {
   global = std::move(global);  // expected-warning{{explicitly moving}}
@@ -31,6 +52,15 @@ void global_int_test() {
                              expected-warning {{unqualified call to 'std::move}}
 }
 
+void global_int_test_mystd() {
+    global = mystd::move(global);  // expected-warning{{explicitly moving}}
+    (global) = mystd::move(global);  // expected-warning{{explicitly moving}}
+
+    using mystd::move;
+    global = move(global); // expected-warning{{explicitly moving}} \
+                           expected-warning {{unqualified call to 'mystd::move}}
+}
+
 class field_test {
   int x;
   field_test(field_test&& other) {
@@ -44,6 +74,19 @@ class field_test {
   }
 };
 
+class field_test_mystd {
+    int x;
+    field_test_mystd(field_test_mystd&& other) {
+        x = mystd::move(x);  // expected-warning{{explicitly moving}}
+        x = mystd::move(other.x);
+        other.x = mystd::move(x);
+        other.x = mystd::move(other.x);  // expected-warning{{explicitly moving}}
+    }
+    void withSuggest(int x) {
+        x = mystd::move(x); // expected-warning{{explicitly moving variable of type 'int' to itself; did you mean to move to member 'x'?}}
+    }
+};
+
 struct A {};
 struct B { A a; };
 struct C { C() {}; ~C() {} };
@@ -58,3 +101,15 @@ void struct_test() {
   C c;
   c = std::move(c);  // expected-warning{{explicitly moving}}
 }
+
+void struct_test_mystd() {
+    A a;
+    a = mystd::move(a);  // expected-warning{{explicitly moving}}
+
+    B b;
+    b = mystd::move(b);  // expected-warning{{explicitly moving}}
+    b.a = mystd::move(b.a);  // expected-warning{{explicitly moving}}
+
+    C c;
+    c = mystd::move(c);  // expected-warning{{explicitly moving}}
+}

>From 638af52d8ef11b37e6c4d1e072793575fe579013 Mon Sep 17 00:00:00 2001
From: MaxEW707 <82551778+MaxEW707 at users.noreply.github.com>
Date: Fri, 29 Dec 2023 17:58:20 -0500
Subject: [PATCH 2/3] formatting fixes

---
 .../SemaCXX/unqualified-std-call-fixits.cpp   |  6 +-
 clang/test/SemaCXX/warn-self-move.cpp         | 56 +++++++++----------
 2 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
index 7e48b40ac346fd..52c3b0b6f13b6f 100644
--- a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
+++ b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
@@ -43,7 +43,7 @@ void g() {
 }
 
 void h() {
-    int i = 0;
-    (void)mymove(i); // no-warning
-    (void)myforward(i); // no-warning
+  int i = 0;
+  (void)mymove(i); // no-warning
+  (void)myforward(i); // no-warning
 }
diff --git a/clang/test/SemaCXX/warn-self-move.cpp b/clang/test/SemaCXX/warn-self-move.cpp
index 3407820b351bf2..c2344c74e6b715 100644
--- a/clang/test/SemaCXX/warn-self-move.cpp
+++ b/clang/test/SemaCXX/warn-self-move.cpp
@@ -33,13 +33,13 @@ void int_test() {
 }
 
 void int_test_mystd() {
-    int x = 5;
-    x = mystd::move(x);  // expected-warning{{explicitly moving}}
-    (x) = mystd::move(x);  // expected-warning{{explicitly moving}}
+  int x = 5;
+  x = mystd::move(x);  // expected-warning{{explicitly moving}}
+  (x) = mystd::move(x);  // expected-warning{{explicitly moving}}
 
-    using mystd::move;
-    x = move(x); // expected-warning{{explicitly moving}} \
-                 expected-warning {{unqualified call to 'mystd::move}}
+  using mystd::move;
+  x = move(x); // expected-warning{{explicitly moving}} \
+                   expected-warning {{unqualified call to 'mystd::move}}
 }
 
 int global;
@@ -53,12 +53,12 @@ void global_int_test() {
 }
 
 void global_int_test_mystd() {
-    global = mystd::move(global);  // expected-warning{{explicitly moving}}
-    (global) = mystd::move(global);  // expected-warning{{explicitly moving}}
+  global = mystd::move(global);  // expected-warning{{explicitly moving}}
+  (global) = mystd::move(global);  // expected-warning{{explicitly moving}}
 
-    using mystd::move;
-    global = move(global); // expected-warning{{explicitly moving}} \
-                           expected-warning {{unqualified call to 'mystd::move}}
+  using mystd::move;
+  global = move(global); // expected-warning{{explicitly moving}} \
+                             expected-warning {{unqualified call to 'mystd::move}}
 }
 
 class field_test {
@@ -75,16 +75,16 @@ class field_test {
 };
 
 class field_test_mystd {
-    int x;
-    field_test_mystd(field_test_mystd&& other) {
-        x = mystd::move(x);  // expected-warning{{explicitly moving}}
-        x = mystd::move(other.x);
-        other.x = mystd::move(x);
-        other.x = mystd::move(other.x);  // expected-warning{{explicitly moving}}
-    }
-    void withSuggest(int x) {
-        x = mystd::move(x); // expected-warning{{explicitly moving variable of type 'int' to itself; did you mean to move to member 'x'?}}
-    }
+  int x;
+  field_test_mystd(field_test_mystd&& other) {
+    x = mystd::move(x);  // expected-warning{{explicitly moving}}
+    x = mystd::move(other.x);
+    other.x = mystd::move(x);
+    other.x = mystd::move(other.x);  // expected-warning{{explicitly moving}}
+  }
+  void withSuggest(int x) {
+    x = mystd::move(x); // expected-warning{{explicitly moving variable of type 'int' to itself; did you mean to move to member 'x'?}}
+  }
 };
 
 struct A {};
@@ -103,13 +103,13 @@ void struct_test() {
 }
 
 void struct_test_mystd() {
-    A a;
-    a = mystd::move(a);  // expected-warning{{explicitly moving}}
+  A a;
+  a = mystd::move(a);  // expected-warning{{explicitly moving}}
 
-    B b;
-    b = mystd::move(b);  // expected-warning{{explicitly moving}}
-    b.a = mystd::move(b.a);  // expected-warning{{explicitly moving}}
+  B b;
+  b = mystd::move(b);  // expected-warning{{explicitly moving}}
+  b.a = mystd::move(b.a);  // expected-warning{{explicitly moving}}
 
-    C c;
-    c = mystd::move(c);  // expected-warning{{explicitly moving}}
+  C c;
+  c = mystd::move(c);  // expected-warning{{explicitly moving}}
 }

>From 8a25a4eb3e94be98aad3c898791ef58c97233666 Mon Sep 17 00:00:00 2001
From: MaxEW707 <82551778+MaxEW707 at users.noreply.github.com>
Date: Sat, 30 Dec 2023 00:34:27 -0500
Subject: [PATCH 3/3] std::string -> twine + SmallString

---
 clang/lib/Sema/SemaExpr.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4adac2e61fdc52..f81eb35caff7c3 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7156,7 +7156,9 @@ static void DiagnosedUnqualifiedCallsToStdFunctions(Sema &S,
           break;
       }
       if (NSD && NSD->getIdentifier()) {
-        std::string Name = NSD->getIdentifier()->getName().str() + "::";
+        SmallString<32> Str;
+        StringRef Name =
+            (NSD->getIdentifier()->getName() + "::").toStringRef(Str);
         S.Diag(DRE->getLocation(),
                diag::warn_unqualified_call_to_std_cast_function)
             << FD->getQualifiedNameAsString()



More information about the cfe-commits mailing list