diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index db17211747b17..5ad72c7026425 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 98a7ecc7fd7df..5d99bb87587ce 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 + [[clang::behaves_like_std("move")]] constexpr remove_reference_t&& move(T &&t) noexcept; + } + + template + [[clang::behaves_like_std("move")]] constexpr remove_reference_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 aebb7d9b945c3..4ebcda089ca30 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 ffbe317d55999..2498af5c7e6a1 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(); 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()) { + 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()) { + if (!InStdNamespace || !isStdCastBuiltin(Context, NewFD, BuiltinID)) + Diag(NewFD->getLocation(), diag::err_attribute_invalid_std_builtin) + << NewFD->getAttr(); + 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 af8b90ecfed97..a48e0c19fce23 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(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 960f513d1111b..f81eb35caff7c 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7145,6 +7145,29 @@ static void DiagnosedUnqualifiedCallsToStdFunctions(Sema &S, if (BuiltinID != Builtin::BImove && BuiltinID != Builtin::BIforward) return; + if (auto *GA = FD->getAttr()) { + if (auto *DC = FD->getDeclContext()) { + const NamespaceDecl *NSD = nullptr; + while (DC->isNamespace()) { + NSD = cast(DC); + if (NSD->isInline()) + DC = NSD->getParent(); + else + break; + } + if (NSD && NSD->getIdentifier()) { + SmallString<32> Str; + StringRef Name = + (NSD->getIdentifier()->getName() + "::").toStringRef(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 6eb7073d67f1c..91f9c3d06f04c 100644 --- a/clang/test/CodeGenCXX/builtin-std-move.cpp +++ b/clang/test/CodeGenCXX/builtin-std-move.cpp @@ -11,6 +11,20 @@ namespace std { template T move(U source, U source_end, T dest); } +namespace mystd { + template [[clang::behaves_like_std("move")]] constexpr T &&move(T &val) { return static_cast(val); } + template [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&move_if_noexcept(T &val); + template [[clang::behaves_like_std("forward")]] constexpr T &&forward(T &val); + template [[clang::behaves_like_std("forward_like")]] constexpr T &&forward_like(T &&val); + template [[clang::behaves_like_std("as_const")]] constexpr const T &as_const(T &val); +} + +template [[clang::behaves_like_std("move")]] constexpr T &&mymove(T &val) { return static_cast(val); } +template [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&mymove_if_noexcept(T &val); +template [[clang::behaves_like_std("forward")]] constexpr T &&myforward(T &val); +template [[clang::behaves_like_std("forward_like")]] constexpr T &&myforward_like(T &&val); +template [[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(a); // CHECK-DAG: @forward_like_a = constant ptr @a T &forward_like_a = std::forward_like(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(a); +// CHECK-DAG: @forward_like_a_2 = constant ptr @a +T &forward_like_a_2 = mystd::forward_like(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(a); +// CHECK-DAG: @forward_like_a_3 = constant ptr @a +T &forward_like_a_3 = myforward_like(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)); + // CHECK: %3 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take_lval(ptr {{.*}} %3) + take_lval(mystd::forward_like(t)); + // CHECK: %4 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take_lval(ptr {{.*}} %4) + take_lval(mystd::as_const(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)); + // CHECK: %3 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take_lval(ptr {{.*}} %3) + take_lval(myforward_like(t)); + // CHECK: %4 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take_lval(ptr {{.*}} %4) + take_lval(myas_const(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 90265186fb3d8..aea494dcb5a94 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 T *addressof(T &); } +namespace mystd { template [[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 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 0000000000000..9374ae9461b04 --- /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 struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template +[[clang::behaves_like_std("moved")]] typename remove_reference::type &&move(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}} + +template +[[clang::behaves_like_std("__builtin_abs")]] typename remove_reference::type &&move2(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}} + +template +[[clang::behaves_like_std("strlen")]] typename remove_reference::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 1a1ffc7ba2e82..52c3b0b6f13b6 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 2c27cd7f95f74..d9c63dda10d1c 100644 --- a/clang/test/SemaCXX/warn-pessmizing-move.cpp +++ b/clang/test/SemaCXX/warn-pessmizing-move.cpp @@ -13,6 +13,16 @@ template typename remove_reference::type &&move(T &&t); } } +namespace mystd { +inline namespace bar { +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template [[clang::behaves_like_std("move")]] typename remove_reference::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@-1{{prevents copy elision}} + // expected-note@-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 2bfc8c9312f02..0617c06702fcd 100644 --- a/clang/test/SemaCXX/warn-redundant-move.cpp +++ b/clang/test/SemaCXX/warn-redundant-move.cpp @@ -13,6 +13,16 @@ template typename remove_reference::type &&move(T &&t); } } +namespace mystd { +inline namespace baz { +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template [[clang::behaves_like_std("move")]] typename remove_reference::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@-1{{redundant move in return statement}} + // expected-note@-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(A()); } + // Warn once here since the type is not dependent. + template + A test2(A a) { + return mystd::move(a); + // expected-warning@-1{{redundant move in return statement}} + // expected-note@-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()); + test2(A()); + } + // T1 and T2 may not be the same, the warning may not always apply. template - T1 test2(T2 t) { + T1 test3(T2 t) { return std::move(t); } - void run_test2() { - test2(A()); - test2(A()); + void run_test3() { + test3(A()); + test3(A()); } } diff --git a/clang/test/SemaCXX/warn-self-move.cpp b/clang/test/SemaCXX/warn-self-move.cpp index 0987e9b6bf601..c2344c74e6b71 100644 --- a/clang/test/SemaCXX/warn-self-move.cpp +++ b/clang/test/SemaCXX/warn-self-move.cpp @@ -11,6 +11,17 @@ template typename remove_reference::type &&move(T &&t); } } +namespace mystd { +inline namespace bar { +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template +[[clang::behaves_like_std("move")]] typename remove_reference::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}} +}