Skip to content
Open
9 changes: 6 additions & 3 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
// those because they are always unnamed.
bool IsZeroSize = Field->isZeroSize(Context);

// P3074
bool TrivialUnion = Context.getLangOpts().CPlusPlus26 && isUnion();

if (const auto *RecordTy = T->getAs<RecordType>()) {
auto *FieldRec = cast<CXXRecordDecl>(RecordTy->getDecl());
if (FieldRec->getDefinition()) {
Expand Down Expand Up @@ -1277,7 +1280,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
// -- for all the non-static data members of its class that are of
// class type (or array thereof), each such class has a trivial
// default constructor.
if (!FieldRec->hasTrivialDefaultConstructor())
if (!FieldRec->hasTrivialDefaultConstructor() && !TrivialUnion)
data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;

// C++0x [class.copy]p13:
Expand Down Expand Up @@ -1315,9 +1318,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasTrivialMoveAssignment())
data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment;

if (!FieldRec->hasTrivialDestructor())
if (!FieldRec->hasTrivialDestructor() && !TrivialUnion)
data().HasTrivialSpecialMembers &= ~SMF_Destructor;
if (!FieldRec->hasTrivialDestructorForCall())
if (!FieldRec->hasTrivialDestructorForCall() && !TrivialUnion)
data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor;
if (!FieldRec->hasIrrelevantDestructor())
data().HasIrrelevantDestructor = false;
Expand Down
50 changes: 49 additions & 1 deletion clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9543,6 +9543,45 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
if (DiagKind == -1)
return false;

if (S.LangOpts.CPlusPlus26 && inUnion() &&
CSM == CXXSpecialMemberKind::Destructor) {
// [class.dtor]/7 In C++26, a destructor for a union X is only deleted under
// the additional conditions that:

// overload resolution to select a constructor to default-initialize an
// object of type X either fails or selects a constructor that is either
// deleted or not trivial, or
// or X has a variant member V of class type M (or possibly
// multi-dimensional array thereof) where V has a default member initializer
// and M has a destructor that is non-trivial,

RecordDecl *Parent = Field->getParent();
while (Parent && (Parent->isAnonymousStructOrUnion() ||
((Parent->isUnion() || Parent->isStruct()) &&
Parent->getIdentifier() == nullptr))) {
if (auto RD = dyn_cast_or_null<RecordDecl>(Parent->getParent()))
Parent = RD;
else
break;
}

auto ParentDecl = cast<CXXRecordDecl>(Parent);
if (!ParentDecl->isBeingDefined()) {
Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember(
ParentDecl, CXXSpecialMemberKind::DefaultConstructor, false, false,
false, false, false);
if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Success) {
CXXConstructorDecl *Ctor =
dyn_cast<CXXConstructorDecl>(SMOR.getMethod());
if (Ctor->isTrivial())
return false;

if (!Ctor->isUserProvided() && !Field->hasInClassInitializer())
return false;
}
}
}

if (Diagnose) {
if (Field) {
S.Diag(Field->getLocation(),
Expand Down Expand Up @@ -9696,6 +9735,10 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {
if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD))
return true;

if (inUnion() && S.LangOpts.CPlusPlus26 &&
CSM == CXXSpecialMemberKind::DefaultConstructor)
return false;

if (CSM == CXXSpecialMemberKind::DefaultConstructor) {
// For a default constructor, all references must be initialized in-class
// and, if a union, it must have a non-const member.
Expand Down Expand Up @@ -9774,7 +9817,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {

// At least one member in each anonymous union must be non-const
if (CSM == CXXSpecialMemberKind::DefaultConstructor &&
AllVariantFieldsAreConst && !FieldRecord->field_empty()) {
AllVariantFieldsAreConst && !FieldRecord->field_empty() &&
!S.LangOpts.CPlusPlus26) {
if (Diagnose)
S.Diag(FieldRecord->getLocation(),
diag::note_deleted_default_ctor_all_const)
Expand Down Expand Up @@ -9804,6 +9848,10 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() {
// default constructor. Don't do that.
if (CSM == CXXSpecialMemberKind::DefaultConstructor && inUnion() &&
AllFieldsAreConst) {

if (S.LangOpts.CPlusPlus26)
return false;

bool AnyFields = false;
for (auto *F : MD->getParent()->fields())
if ((AnyFields = !F->isUnnamedBitField()))
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CXX/drs/cwg6xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,11 @@ namespace cwg667 { // cwg667: 8

struct B { ~B() = delete; };
union C { B b; };
#if __cplusplus > 202302L
static_assert(__is_trivially_destructible(C), "");
#else
static_assert(!__is_trivially_destructible(C), "");
#endif

struct D { D(const D&) = delete; };
struct E : D {};
Expand Down
16 changes: 9 additions & 7 deletions clang/test/CXX/special/class.ctor/p5-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++23 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected %s -std=c++26 -Wno-deprecated-builtins -Wno-defaulted-function-deleted

struct DefaultedDefCtor1 {};
struct DefaultedDefCtor2 { DefaultedDefCtor2() = default; };
Expand All @@ -23,8 +25,8 @@ int n;

// - X is a union-like class that has a variant member with a non-trivial
// default constructor,
union Deleted1a { UserProvidedDefCtor u; }; // expected-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}}
Deleted1a d1a; // expected-error {{implicitly-deleted default constructor}}
union Deleted1a { UserProvidedDefCtor u; }; // cxx11-23-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}}
Deleted1a d1a; // cxx11-23-error {{implicitly-deleted default constructor}}
union NotDeleted1a { DefaultedDefCtor1 nu; };
NotDeleted1a nd1a;
union NotDeleted1b { DefaultedDefCtor2 nu; };
Expand Down Expand Up @@ -86,19 +88,19 @@ NotDeleted3i nd3i;
union Deleted4a {
const int a;
const int b;
const UserProvidedDefCtor c; // expected-note {{because variant field 'c' has a non-trivial default constructor}}
const UserProvidedDefCtor c; // cxx11-23-note {{because variant field 'c' has a non-trivial default constructor}}
};
Deleted4a d4a; // expected-error {{implicitly-deleted default constructor}}
Deleted4a d4a; // cxx11-23-error {{implicitly-deleted default constructor}}
union NotDeleted4a { const int a; int b; };
NotDeleted4a nd4a;

// - X is a non-union class and all members of any anonymous union member are of
// const-qualified type (or array thereof),
struct Deleted5a {
union { const int a; }; // expected-note {{because all data members of an anonymous union member are const-qualified}}
union { const int a; }; // cxx11-23-note {{because all data members of an anonymous union member are const-qualified}}
union { int b; };
};
Deleted5a d5a; // expected-error {{implicitly-deleted default constructor}}
Deleted5a d5a; // cxx11-23-error {{implicitly-deleted default constructor}}
struct NotDeleted5a { union { const int a; int b; }; union { const int c; int d; }; };
NotDeleted5a nd5a;

Expand Down
28 changes: 15 additions & 13 deletions clang/test/CXX/special/class.ctor/p6-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 %s -std=c++26

// Implicitly-defined default constructors are constexpr if the implicit
// definition would be.
Expand All @@ -15,8 +16,9 @@ constexpr NonConstexpr2a nc2a = NonConstexpr2a(); // ok, does not call construct
constexpr int nc2_a = NonConstexpr2().nl.a; // ok
constexpr int nc2a_a = NonConstexpr2a().a; // ok
struct Helper {
friend constexpr NonConstexpr1::NonConstexpr1(); // expected-error {{follows non-constexpr declaration}}
friend constexpr NonConstexpr2::NonConstexpr2(); // expected-error {{follows non-constexpr declaration}}
friend constexpr NonConstexpr1::NonConstexpr1(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That ‘is missing exception specification 'noexcept'’ error is weird; I’m also getting that before C++26 and only Clang errors on this; GCC, MSVC, and EDG seem to be fine with it. I don’t think that has anything to do w/ this patch but it’s still strange.

friend constexpr NonConstexpr2::NonConstexpr2(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}}

};

struct Constexpr1 {};
Expand All @@ -31,14 +33,14 @@ constexpr Constexpr2 c2 = Constexpr2(); // ok

int n;
struct Member {
Member() : a(n) {}
Member() : a(n) {} // cxx26-note {{here}}
constexpr Member(int&a) : a(a) {}
int &a;
};
struct NonConstexpr4 { // expected-note {{here}}
struct NonConstexpr4 { // cxx11-23-note {{here}} cxx26-note {{non-constexpr constructor}}
Member m;
};
constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} expected-note {{non-constexpr constructor 'NonConstexpr4'}}
constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} cxx11-23-note {{non-constexpr constructor 'NonConstexpr4'}} cxx26-note {{in call to}}
struct Constexpr3 {
constexpr Constexpr3() : m(n) {}
Member m;
Expand All @@ -53,11 +55,11 @@ constexpr Constexpr4 c4 = Constexpr4(); // ok
// This rule breaks some legal C++98 programs!
struct A {}; // expected-note {{here}}
struct B {
friend A::A(); // expected-error {{non-constexpr declaration of 'A' follows constexpr declaration}}
friend A::A(); // cxx11-23-error {{non-constexpr declaration of 'A' follows constexpr declaration}} cxx26-error {{missing exception specification}}
};

namespace UnionCtors {
union A { // expected-note {{here}}
union A { // cxx11-23-note {{here}}
int a;
int b;
};
Expand All @@ -79,19 +81,19 @@ namespace UnionCtors {
int d = 5;
};
};
struct E { // expected-note {{here}}
struct E { // cxx11-23-note {{here}}
union {
int a;
int b;
};
};

struct Test {
friend constexpr A::A() noexcept; // expected-error {{follows non-constexpr declaration}}
friend constexpr A::A() noexcept; // cxx11-23-error {{follows non-constexpr declaration}}
friend constexpr B::B() noexcept;
friend constexpr C::C() noexcept;
friend constexpr D::D() noexcept;
friend constexpr E::E() noexcept; // expected-error {{follows non-constexpr declaration}}
friend constexpr E::E() noexcept; // cxx11-23-error {{follows non-constexpr declaration}}
};
}

Expand Down Expand Up @@ -122,6 +124,6 @@ namespace PR48763 {

struct G { G(); };
struct H : D { using D::D; H(int); G g; };
union V { H h; }; // expected-note {{field 'h' has a non-trivial default constructor}}
V v; // expected-error {{deleted}}
union V { H h; }; // cxx11-23-note {{field 'h' has a non-trivial default constructor}}
V v; // cxx11-23-error {{deleted}}
}
3 changes: 2 additions & 1 deletion clang/test/CXX/special/class.dtor/p5-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -verify -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
// RUN: %clang_cc1 -verify=expected -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
// RUN: %clang_cc1 -verify=expected -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu

struct NonTrivDtor {
~NonTrivDtor();
Expand Down
96 changes: 96 additions & 0 deletions clang/test/CXX/special/class.dtor/p7.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// RUN: %clang_cc1 -verify -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu

struct NonTrivial {
int i;
constexpr NonTrivial(int i) :i(i) { }
constexpr ~NonTrivial() { }
};

union U0 {
NonTrivial nt;
int i;
};
U0 u0;

// check for constant evaluation failure
constexpr NonTrivial make() {
U0 u0;
return u0.nt;
}
constexpr NonTrivial nt = make(); // expected-error {{must be initialized by a constant expression}}}
// expected-note@-3 {{union with no active member}}
// expected-note@-4 {{in call to 'NonTrivial(u0.nt)'}}
// expected-note@-3 {{in call to 'make()'}}

// overload resolution to select a constructor to default-initialize an object of type X either fails
union U1 {
U1(int);
NonTrivial nt; // #1
};
U1 u1(1); // expected-error {{deleted function}} expected-note@#1 {{non-trivial destructor}}

// or selects a constructor that is either deleted or not trivial, or
union U2 {
U2() : nt(2) { }
NonTrivial nt; // #2
};
U2 u2; // expected-error {{deleted function}} expected-note@#2 {{non-trivial destructor}}

union U3 {
U3() = delete;
U3(int);
NonTrivial nt; // #3
};
U3 u3(1); // expected-error {{deleted function}} expected-note@#3 {{non-trivial destructor}}

// or X has a variant member V of class type M (or possibly multi-dimensional array thereof) where V has a default member initializer and M has a destructor that is non-trivial,
union U4 {
NonTrivial nt = 1; // #4
};
U4 u4; // expected-error {{deleted function}} expected-note@#4 {{non-trivial destructor}}

union U5 {
NonTrivial nt[2] = {1, 2}; // #5
};
U5 u5; // expected-error {{deleted function}} expected-note@#5 {{non-trivial destructor}}

union U6 {
NonTrivial nt;
U6* next = nullptr;
};
U6 u6;

union U7 {
U7() = default;
NonTrivial nt;
U6* next = nullptr;
};
U7 u7;

union U8 {
struct {
NonTrivial x;
};
} u8;

union U9 {
struct {
NonTrivial x = 1; // #6
};
} u9; // expected-error {{deleted function}}

struct DeletedDtor {
~DeletedDtor() = delete; // expected-note 2 {{deleted here}}
};
union B1 {
B1();
DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}}
};
B1 b1; // expected-error {{deleted function}}
union B2 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more cases:

union A {
    struct {
        NonTrivial x; // W/ and w/o a default member initialiser.
    };
};
A a;

struct B {
    union {
        NonTrivial x; // as above
    };
};
B b;

union {
    struct {
        union {
            struct {
                union {
                    NonTrivial x; // as above
                };
            };
        };
    };
} c;

struct {
    union {
        struct {
            union {
                struct {
                    NonTrivial x; // as above
                };
            };
        };
    };
} d;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright I'm not handling this case correctly, but I can't figure out why:

union U9 {
    struct {
        NonTrivial x = 1; // #6
    };
} u9; // expected-error {{deleted function}}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, at a glance nothing jumps out to me; we do create a FieldDecl for the anonymous struct inside the union so I’d expect this to just handle that case—maybe we’re doing something weird with IndirectFieldDecls somewhere but that’s just a guess.

I did spot a fixme about anonymous unions inside anonymous unions, so er, here’s another gross test case I suppose:

union {
    union {
        NonTrivial x;
    };
} a;

B2();
union { // expected-note {{deleted destructor}}
DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}}
};
};
B2 b2; // expected-error {{deleted function}}
Loading