52 changes: 25 additions & 27 deletions clang/lib/CodeGen/MicrosoftCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,46 +819,44 @@ MicrosoftCXXABI::getRecordArgABI(const CXXRecordDecl *RD) const {
return RAA_Default;

case llvm::Triple::x86_64:
// Win64 passes objects with non-trivial copy ctors indirectly.
if (RD->hasNonTrivialCopyConstructor())
return RAA_Indirect;

// If an object has a destructor, we'd really like to pass it indirectly
// If a class has a destructor, we'd really like to pass it indirectly
// because it allows us to elide copies. Unfortunately, MSVC makes that
// impossible for small types, which it will pass in a single register or
// stack slot. Most objects with dtors are large-ish, so handle that early.
// We can't call out all large objects as being indirect because there are
// multiple x64 calling conventions and the C++ ABI code shouldn't dictate
// how we pass large POD types.
//
// Note: This permits small classes with nontrivial destructors to be
// passed in registers, which is non-conforming.
if (RD->hasNonTrivialDestructor() &&
getContext().getTypeSize(RD->getTypeForDecl()) > 64)
return RAA_Indirect;

// If this is true, the implicit copy constructor that Sema would have
// created would not be deleted. FIXME: We should provide a more direct way
// for CodeGen to ask whether the constructor was deleted.
if (!RD->hasUserDeclaredCopyConstructor() &&
!RD->hasUserDeclaredMoveConstructor() &&
!RD->needsOverloadResolutionForMoveConstructor() &&
!RD->hasUserDeclaredMoveAssignment() &&
!RD->needsOverloadResolutionForMoveAssignment())
return RAA_Default;

// Otherwise, Sema should have created an implicit copy constructor if
// needed.
assert(!RD->needsImplicitCopyConstructor());

// We have to make sure the trivial copy constructor isn't deleted.
for (const CXXConstructorDecl *CD : RD->ctors()) {
if (CD->isCopyConstructor()) {
assert(CD->isTrivial());
// We had at least one undeleted trivial copy ctor. Return directly.
if (!CD->isDeleted())
return RAA_Default;
// If a class has at least one non-deleted, trivial copy constructor, it
// is passed according to the C ABI. Otherwise, it is passed indirectly.
//
// Note: This permits classes with non-trivial copy or move ctors to be
// passed in registers, so long as they *also* have a trivial copy ctor,
// which is non-conforming.
if (RD->needsImplicitCopyConstructor()) {
// If the copy ctor has not yet been declared, we can read its triviality
// off the AST.
if (!RD->defaultedCopyConstructorIsDeleted() &&
RD->hasTrivialCopyConstructor())
return RAA_Default;
} else {
// Otherwise, we need to find the copy constructor(s) and ask.
for (const CXXConstructorDecl *CD : RD->ctors()) {
if (CD->isCopyConstructor()) {
// We had at least one nondeleted trivial copy ctor. Return directly.
if (!CD->isDeleted() && CD->isTrivial())
return RAA_Default;
}
}
}

// The trivial copy constructor was deleted. Return indirectly.
// We have no trivial, non-deleted copy constructor.
return RAA_Indirect;
}

Expand Down
56 changes: 53 additions & 3 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5726,6 +5726,53 @@ static void DefineImplicitSpecialMember(Sema &S, CXXMethodDecl *MD,
}
}

/// Determine whether a type is permitted to be passed or returned in
/// registers, per C++ [class.temporary]p3.
static bool computeCanPassInRegisters(Sema &S, CXXRecordDecl *D) {
if (D->isDependentType() || D->isInvalidDecl())
return false;

// Per C++ [class.temporary]p3, the relevant condition is:
// each copy constructor, move constructor, and destructor of X is
// either trivial or deleted, and X has at least one non-deleted copy
// or move constructor
bool HasNonDeletedCopyOrMove = false;

if (D->needsImplicitCopyConstructor() &&
!D->defaultedCopyConstructorIsDeleted()) {
if (!D->hasTrivialCopyConstructor())
return false;
HasNonDeletedCopyOrMove = true;
}

if (S.getLangOpts().CPlusPlus11 && D->needsImplicitMoveConstructor() &&
!D->defaultedMoveConstructorIsDeleted()) {
if (!D->hasTrivialMoveConstructor())
return false;
HasNonDeletedCopyOrMove = true;
}

if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() &&
!D->hasTrivialDestructor())
return false;

for (const CXXMethodDecl *MD : D->methods()) {
if (MD->isDeleted())
continue;

auto *CD = dyn_cast<CXXConstructorDecl>(MD);
if (CD && CD->isCopyOrMoveConstructor())
HasNonDeletedCopyOrMove = true;
else if (!isa<CXXDestructorDecl>(MD))
continue;

if (!MD->isTrivial())
return false;
}

return HasNonDeletedCopyOrMove;
}

/// \brief Perform semantic checks on a class definition that has been
/// completing, introducing implicitly-declared members, checking for
/// abstract types, etc.
Expand Down Expand Up @@ -5870,6 +5917,8 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
}

checkClassLevelDLLAttribute(Record);

Record->setCanPassInRegisters(computeCanPassInRegisters(*this, Record));
}

/// Look up the special member function that would be called by a special
Expand Down Expand Up @@ -7496,8 +7545,7 @@ void Sema::ActOnFinishCXXMemberSpecification(Scope* S, SourceLocation RLoc,
reinterpret_cast<Decl**>(FieldCollector->getCurFields()),
FieldCollector->getCurNumFields()), LBrac, RBrac, AttrList);

CheckCompletedCXXClass(
dyn_cast_or_null<CXXRecordDecl>(TagDecl));
CheckCompletedCXXClass(dyn_cast_or_null<CXXRecordDecl>(TagDecl));
}

/// AddImplicitlyDeclaredMembersToClass - Adds any implicitly-declared
Expand Down Expand Up @@ -11929,8 +11977,10 @@ CXXConstructorDecl *Sema::DeclareImplicitCopyConstructor(
Scope *S = getScopeForContext(ClassDecl);
CheckImplicitSpecialMemberDeclaration(S, CopyConstructor);

if (ShouldDeleteSpecialMember(CopyConstructor, CXXCopyConstructor))
if (ShouldDeleteSpecialMember(CopyConstructor, CXXCopyConstructor)) {
ClassDecl->setImplicitCopyConstructorIsDeleted();
SetDeclDeleted(CopyConstructor, ClassLoc);
}

if (S)
PushOnScopeChains(CopyConstructor, S, false);
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1559,9 +1559,11 @@ void ASTDeclReader::ReadCXXDefinitionData(
Data.HasUninitializedFields = Record.readInt();
Data.HasInheritedConstructor = Record.readInt();
Data.HasInheritedAssignment = Record.readInt();
Data.NeedOverloadResolutionForCopyConstructor = Record.readInt();
Data.NeedOverloadResolutionForMoveConstructor = Record.readInt();
Data.NeedOverloadResolutionForMoveAssignment = Record.readInt();
Data.NeedOverloadResolutionForDestructor = Record.readInt();
Data.DefaultedCopyConstructorIsDeleted = Record.readInt();
Data.DefaultedMoveConstructorIsDeleted = Record.readInt();
Data.DefaultedMoveAssignmentIsDeleted = Record.readInt();
Data.DefaultedDestructorIsDeleted = Record.readInt();
Expand All @@ -1570,6 +1572,7 @@ void ASTDeclReader::ReadCXXDefinitionData(
Data.HasIrrelevantDestructor = Record.readInt();
Data.HasConstexprNonCopyMoveConstructor = Record.readInt();
Data.HasDefaultedDefaultConstructor = Record.readInt();
Data.CanPassInRegisters = Record.readInt();
Data.DefaultedDefaultConstructorIsConstexpr = Record.readInt();
Data.HasConstexprDefaultConstructor = Record.readInt();
Data.HasNonLiteralTypeFieldsOrBases = Record.readInt();
Expand Down Expand Up @@ -1697,9 +1700,11 @@ void ASTDeclReader::MergeDefinitionData(
MATCH_FIELD(HasUninitializedFields)
MATCH_FIELD(HasInheritedConstructor)
MATCH_FIELD(HasInheritedAssignment)
MATCH_FIELD(NeedOverloadResolutionForCopyConstructor)
MATCH_FIELD(NeedOverloadResolutionForMoveConstructor)
MATCH_FIELD(NeedOverloadResolutionForMoveAssignment)
MATCH_FIELD(NeedOverloadResolutionForDestructor)
MATCH_FIELD(DefaultedCopyConstructorIsDeleted)
MATCH_FIELD(DefaultedMoveConstructorIsDeleted)
MATCH_FIELD(DefaultedMoveAssignmentIsDeleted)
MATCH_FIELD(DefaultedDestructorIsDeleted)
Expand All @@ -1708,6 +1713,7 @@ void ASTDeclReader::MergeDefinitionData(
MATCH_FIELD(HasIrrelevantDestructor)
OR_FIELD(HasConstexprNonCopyMoveConstructor)
OR_FIELD(HasDefaultedDefaultConstructor)
MATCH_FIELD(CanPassInRegisters)
MATCH_FIELD(DefaultedDefaultConstructorIsConstexpr)
OR_FIELD(HasConstexprDefaultConstructor)
MATCH_FIELD(HasNonLiteralTypeFieldsOrBases)
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5875,9 +5875,11 @@ void ASTRecordWriter::AddCXXDefinitionData(const CXXRecordDecl *D) {
Record->push_back(Data.HasUninitializedFields);
Record->push_back(Data.HasInheritedConstructor);
Record->push_back(Data.HasInheritedAssignment);
Record->push_back(Data.NeedOverloadResolutionForCopyConstructor);
Record->push_back(Data.NeedOverloadResolutionForMoveConstructor);
Record->push_back(Data.NeedOverloadResolutionForMoveAssignment);
Record->push_back(Data.NeedOverloadResolutionForDestructor);
Record->push_back(Data.DefaultedCopyConstructorIsDeleted);
Record->push_back(Data.DefaultedMoveConstructorIsDeleted);
Record->push_back(Data.DefaultedMoveAssignmentIsDeleted);
Record->push_back(Data.DefaultedDestructorIsDeleted);
Expand All @@ -5886,6 +5888,7 @@ void ASTRecordWriter::AddCXXDefinitionData(const CXXRecordDecl *D) {
Record->push_back(Data.HasIrrelevantDestructor);
Record->push_back(Data.HasConstexprNonCopyMoveConstructor);
Record->push_back(Data.HasDefaultedDefaultConstructor);
Record->push_back(Data.CanPassInRegisters);
Record->push_back(Data.DefaultedDefaultConstructorIsConstexpr);
Record->push_back(Data.HasConstexprDefaultConstructor);
Record->push_back(Data.HasNonLiteralTypeFieldsOrBases);
Expand Down
137 changes: 104 additions & 33 deletions clang/test/CodeGenCXX/uncopyable-args.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows-msvc -emit-llvm -o - %s | FileCheck %s -check-prefix=WIN64
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows-msvc -emit-llvm -o - %s -fms-compatibility -fms-compatibility-version=18 | FileCheck %s -check-prefix=WIN64 -check-prefix=WIN64-18
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows-msvc -emit-llvm -o - %s -fms-compatibility -fms-compatibility-version=19 | FileCheck %s -check-prefix=WIN64 -check-prefix=WIN64-19

namespace trivial {
// Trivial structs should be passed directly.
Expand Down Expand Up @@ -52,12 +53,11 @@ void foo(A);
void bar() {
foo({});
}
// FIXME: The copy ctor is implicitly deleted.
// CHECK-DISABLED-LABEL: define void @_ZN9move_ctor3barEv()
// CHECK-DISABLED: call void @_Z{{.*}}C1Ev(
// CHECK-DISABLED-NOT: call
// CHECK-DISABLED: call void @_ZN9move_ctor3fooENS_1AE(%"struct.move_ctor::A"* %{{.*}})
// CHECK-DISABLED-LABEL: declare void @_ZN9move_ctor3fooENS_1AE(%"struct.move_ctor::A"*)
// CHECK-LABEL: define void @_ZN9move_ctor3barEv()
// CHECK: call void @_Z{{.*}}C1Ev(
// CHECK-NOT: call
// CHECK: call void @_ZN9move_ctor3fooENS_1AE(%"struct.move_ctor::A"* %{{.*}})
// CHECK-LABEL: declare void @_ZN9move_ctor3fooENS_1AE(%"struct.move_ctor::A"*)

// WIN64-LABEL: declare void @"\01?foo@move_ctor@@YAXUA@1@@Z"(%"struct.move_ctor::A"*)
}
Expand All @@ -73,12 +73,11 @@ void foo(A);
void bar() {
foo({});
}
// FIXME: The copy ctor is deleted.
// CHECK-DISABLED-LABEL: define void @_ZN11all_deleted3barEv()
// CHECK-DISABLED: call void @_Z{{.*}}C1Ev(
// CHECK-DISABLED-NOT: call
// CHECK-DISABLED: call void @_ZN11all_deleted3fooENS_1AE(%"struct.all_deleted::A"* %{{.*}})
// CHECK-DISABLED-LABEL: declare void @_ZN11all_deleted3fooENS_1AE(%"struct.all_deleted::A"*)
// CHECK-LABEL: define void @_ZN11all_deleted3barEv()
// CHECK: call void @_Z{{.*}}C1Ev(
// CHECK-NOT: call
// CHECK: call void @_ZN11all_deleted3fooENS_1AE(%"struct.all_deleted::A"* %{{.*}})
// CHECK-LABEL: declare void @_ZN11all_deleted3fooENS_1AE(%"struct.all_deleted::A"*)

// WIN64-LABEL: declare void @"\01?foo@all_deleted@@YAXUA@1@@Z"(%"struct.all_deleted::A"*)
}
Expand All @@ -93,14 +92,15 @@ void foo(A);
void bar() {
foo({});
}
// FIXME: The copy and move ctors are implicitly deleted.
// CHECK-DISABLED-LABEL: define void @_ZN18implicitly_deleted3barEv()
// CHECK-DISABLED: call void @_Z{{.*}}C1Ev(
// CHECK-DISABLED-NOT: call
// CHECK-DISABLED: call void @_ZN18implicitly_deleted3fooENS_1AE(%"struct.implicitly_deleted::A"* %{{.*}})
// CHECK-DISABLED-LABEL: declare void @_ZN18implicitly_deleted3fooENS_1AE(%"struct.implicitly_deleted::A"*)

// WIN64-LABEL: declare void @"\01?foo@implicitly_deleted@@YAXUA@1@@Z"(%"struct.implicitly_deleted::A"*)
// CHECK-LABEL: define void @_ZN18implicitly_deleted3barEv()
// CHECK: call void @_Z{{.*}}C1Ev(
// CHECK-NOT: call
// CHECK: call void @_ZN18implicitly_deleted3fooENS_1AE(%"struct.implicitly_deleted::A"* %{{.*}})
// CHECK-LABEL: declare void @_ZN18implicitly_deleted3fooENS_1AE(%"struct.implicitly_deleted::A"*)

// In MSVC 2013, the copy ctor is not deleted by a move assignment. In MSVC 2015, it is.
// WIN64-18-LABEL: declare void @"\01?foo@implicitly_deleted@@YAXUA@1@@Z"(i64
// WIN64-19-LABEL: declare void @"\01?foo@implicitly_deleted@@YAXUA@1@@Z"(%"struct.implicitly_deleted::A"*)
}

namespace one_deleted {
Expand All @@ -113,12 +113,11 @@ void foo(A);
void bar() {
foo({});
}
// FIXME: The copy constructor is implicitly deleted.
// CHECK-DISABLED-LABEL: define void @_ZN11one_deleted3barEv()
// CHECK-DISABLED: call void @_Z{{.*}}C1Ev(
// CHECK-DISABLED-NOT: call
// CHECK-DISABLED: call void @_ZN11one_deleted3fooENS_1AE(%"struct.one_deleted::A"* %{{.*}})
// CHECK-DISABLED-LABEL: declare void @_ZN11one_deleted3fooENS_1AE(%"struct.one_deleted::A"*)
// CHECK-LABEL: define void @_ZN11one_deleted3barEv()
// CHECK: call void @_Z{{.*}}C1Ev(
// CHECK-NOT: call
// CHECK: call void @_ZN11one_deleted3fooENS_1AE(%"struct.one_deleted::A"* %{{.*}})
// CHECK-LABEL: declare void @_ZN11one_deleted3fooENS_1AE(%"struct.one_deleted::A"*)

// WIN64-LABEL: declare void @"\01?foo@one_deleted@@YAXUA@1@@Z"(%"struct.one_deleted::A"*)
}
Expand Down Expand Up @@ -195,12 +194,10 @@ void foo(B);
void bar() {
foo({});
}
// FIXME: This class has a non-trivial copy ctor and a trivial copy ctor. It's
// not clear whether we should pass by address or in registers.
// CHECK-DISABLED-LABEL: define void @_ZN14two_copy_ctors3barEv()
// CHECK-DISABLED: call void @_Z{{.*}}C1Ev(
// CHECK-DISABLED: call void @_ZN14two_copy_ctors3fooENS_1BE(%"struct.two_copy_ctors::B"* %{{.*}})
// CHECK-DISABLED-LABEL: declare void @_ZN14two_copy_ctors3fooENS_1BE(%"struct.two_copy_ctors::B"*)
// CHECK-LABEL: define void @_ZN14two_copy_ctors3barEv()
// CHECK: call void @_Z{{.*}}C1Ev(
// CHECK: call void @_ZN14two_copy_ctors3fooENS_1BE(%"struct.two_copy_ctors::B"* %{{.*}})
// CHECK-LABEL: declare void @_ZN14two_copy_ctors3fooENS_1BE(%"struct.two_copy_ctors::B"*)

// WIN64-LABEL: declare void @"\01?foo@two_copy_ctors@@YAXUB@1@@Z"(%"struct.two_copy_ctors::B"*)
}
Expand All @@ -212,6 +209,7 @@ struct A {
void *p;
};
void *foo(A a) { return a.p; }
// CHECK-LABEL: define i8* @_ZN15definition_only3fooENS_1AE(%"struct.definition_only::A"*
// WIN64-LABEL: define i8* @"\01?foo@definition_only@@YAPEAXUA@1@@Z"(%"struct.definition_only::A"*
}

Expand All @@ -226,6 +224,7 @@ struct A {
B b;
};
void *foo(A a) { return a.b.p; }
// CHECK-LABEL: define i8* @_ZN17deleted_by_member3fooENS_1AE(%"struct.deleted_by_member::A"*
// WIN64-LABEL: define i8* @"\01?foo@deleted_by_member@@YAPEAXUA@1@@Z"(%"struct.deleted_by_member::A"*
}

Expand All @@ -239,6 +238,7 @@ struct A : B {
A();
};
void *foo(A a) { return a.p; }
// CHECK-LABEL: define i8* @_ZN15deleted_by_base3fooENS_1AE(%"struct.deleted_by_base::A"*
// WIN64-LABEL: define i8* @"\01?foo@deleted_by_base@@YAPEAXUA@1@@Z"(%"struct.deleted_by_base::A"*
}

Expand All @@ -253,6 +253,7 @@ struct A {
B b;
};
void *foo(A a) { return a.b.p; }
// CHECK-LABEL: define i8* @_ZN22deleted_by_member_copy3fooENS_1AE(%"struct.deleted_by_member_copy::A"*
// WIN64-LABEL: define i8* @"\01?foo@deleted_by_member_copy@@YAPEAXUA@1@@Z"(%"struct.deleted_by_member_copy::A"*
}

Expand All @@ -266,6 +267,7 @@ struct A : B {
A();
};
void *foo(A a) { return a.p; }
// CHECK-LABEL: define i8* @_ZN20deleted_by_base_copy3fooENS_1AE(%"struct.deleted_by_base_copy::A"*
// WIN64-LABEL: define i8* @"\01?foo@deleted_by_base_copy@@YAPEAXUA@1@@Z"(%"struct.deleted_by_base_copy::A"*
}

Expand All @@ -275,6 +277,75 @@ struct A {
A(const A &o) = delete;
void *p;
};
// CHECK-LABEL: define i8* @_ZN15explicit_delete3fooENS_1AE(%"struct.explicit_delete::A"*
// WIN64-LABEL: define i8* @"\01?foo@explicit_delete@@YAPEAXUA@1@@Z"(%"struct.explicit_delete::A"*
void *foo(A a) { return a.p; }
}

namespace implicitly_deleted_copy_ctor {
struct A {
// No move ctor due to copy assignment.
A &operator=(const A&);
// Deleted copy ctor due to rvalue ref member.
int &&ref;
};
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1AE(%"struct.implicitly_deleted_copy_ctor::A"*
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAAEAHUA@1@@Z"(%"struct.implicitly_deleted_copy_ctor::A"*
int &foo(A a) { return a.ref; }

struct B {
// Passed direct: has non-deleted trivial copy ctor.
B &operator=(const B&);
int &ref;
};
int &foo(B b) { return b.ref; }
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1BE(i32*
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAAEAHUB@1@@Z"(i64

struct X { X(const X&); };
struct Y { Y(const Y&) = default; };

union C {
C &operator=(const C&);
// Passed indirect: copy ctor deleted due to variant member with nontrivial copy ctor.
X x;
int n;
};
int foo(C c) { return c.n; }
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1CE(%"union.implicitly_deleted_copy_ctor::C"*
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAHTC@1@@Z"(%"union.implicitly_deleted_copy_ctor::C"*

struct D {
D &operator=(const D&);
// Passed indirect: copy ctor deleted due to variant member with nontrivial copy ctor.
union {
X x;
int n;
};
};
int foo(D d) { return d.n; }
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1DE(%"struct.implicitly_deleted_copy_ctor::D"*
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAHUD@1@@Z"(%"struct.implicitly_deleted_copy_ctor::D"*

union E {
// Passed direct: has non-deleted trivial copy ctor.
E &operator=(const E&);
Y y;
int n;
};
int foo(E e) { return e.n; }
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1EE(i32
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAHTE@1@@Z"(i32

struct F {
// Passed direct: has non-deleted trivial copy ctor.
F &operator=(const F&);
union {
Y y;
int n;
};
};
int foo(F f) { return f.n; }
// CHECK-LABEL: define {{.*}} @_ZN28implicitly_deleted_copy_ctor3fooENS_1FE(i32
// WIN64-LABEL: define {{.*}} @"\01?foo@implicitly_deleted_copy_ctor@@YAHUF@1@@Z"(i32
}
49 changes: 29 additions & 20 deletions clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,26 +1108,35 @@ TEST(ConstructorDeclaration, IsExplicit) {
}

TEST(ConstructorDeclaration, Kinds) {
EXPECT_TRUE(matches("struct S { S(); };",
cxxConstructorDecl(isDefaultConstructor())));
EXPECT_TRUE(notMatches("struct S { S(); };",
cxxConstructorDecl(isCopyConstructor())));
EXPECT_TRUE(notMatches("struct S { S(); };",
cxxConstructorDecl(isMoveConstructor())));

EXPECT_TRUE(notMatches("struct S { S(const S&); };",
cxxConstructorDecl(isDefaultConstructor())));
EXPECT_TRUE(matches("struct S { S(const S&); };",
cxxConstructorDecl(isCopyConstructor())));
EXPECT_TRUE(notMatches("struct S { S(const S&); };",
cxxConstructorDecl(isMoveConstructor())));

EXPECT_TRUE(notMatches("struct S { S(S&&); };",
cxxConstructorDecl(isDefaultConstructor())));
EXPECT_TRUE(notMatches("struct S { S(S&&); };",
cxxConstructorDecl(isCopyConstructor())));
EXPECT_TRUE(matches("struct S { S(S&&); };",
cxxConstructorDecl(isMoveConstructor())));
EXPECT_TRUE(matches(
"struct S { S(); };",
cxxConstructorDecl(isDefaultConstructor(), unless(isImplicit()))));
EXPECT_TRUE(notMatches(
"struct S { S(); };",
cxxConstructorDecl(isCopyConstructor(), unless(isImplicit()))));
EXPECT_TRUE(notMatches(
"struct S { S(); };",
cxxConstructorDecl(isMoveConstructor(), unless(isImplicit()))));

EXPECT_TRUE(notMatches(
"struct S { S(const S&); };",
cxxConstructorDecl(isDefaultConstructor(), unless(isImplicit()))));
EXPECT_TRUE(matches(
"struct S { S(const S&); };",
cxxConstructorDecl(isCopyConstructor(), unless(isImplicit()))));
EXPECT_TRUE(notMatches(
"struct S { S(const S&); };",
cxxConstructorDecl(isMoveConstructor(), unless(isImplicit()))));

EXPECT_TRUE(notMatches(
"struct S { S(S&&); };",
cxxConstructorDecl(isDefaultConstructor(), unless(isImplicit()))));
EXPECT_TRUE(notMatches(
"struct S { S(S&&); };",
cxxConstructorDecl(isCopyConstructor(), unless(isImplicit()))));
EXPECT_TRUE(matches(
"struct S { S(S&&); };",
cxxConstructorDecl(isMoveConstructor(), unless(isImplicit()))));
}

TEST(ConstructorDeclaration, IsUserProvided) {
Expand Down