https://github.com/love1angel updated https://github.com/llvm/llvm-project/pull/185886
>From 6b435c9b2296217570596c015ca17dd10520cd80 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 11 Mar 2026 14:45:40 +0800 Subject: [PATCH 1/6] [Clang] Implement P3074R7: trivial unions (C++26) P3074R7 makes union special member functions trivial by default in C++26, regardless of variant member triviality. A defaulted destructor for a union is now defined as deleted only if its default constructor is user-provided (or deleted/ambiguous), or if a variant member has both a default member initializer and a non-trivial destructor. The pre-existing rule that a deleted or inaccessible member destructor causes the union destructor to be deleted (p7.2) is preserved. This patch does not implement the implicit-lifetime-start semantics from P3074R7 p4, which was reworked by P3726R1 into __builtin_start_lifetime. Defines __cpp_trivial_union=202502L (bumped to 202602L by P3726R1). --- .../clang/AST/CXXRecordDeclDefinitionBits.def | 5 + clang/include/clang/AST/DeclCXX.h | 7 + .../clang/Basic/DiagnosticSemaKinds.td | 6 + clang/lib/AST/DeclCXX.cpp | 46 +++- clang/lib/Frontend/InitPreprocessor.cpp | 4 + clang/lib/Sema/SemaDeclCXX.cpp | 112 +++++++- clang/test/CXX/drs/cwg14xx.cpp | 68 +++-- clang/test/CXX/special/class.ctor/p5-0x.cpp | 16 +- clang/test/CXX/special/class.ctor/p6-0x.cpp | 31 +-- clang/test/CXX/special/class.dtor/p5-0x.cpp | 33 +-- clang/test/SemaCXX/cxx26-trivial-union.cpp | 253 ++++++++++++++++++ 11 files changed, 512 insertions(+), 69 deletions(-) create mode 100644 clang/test/SemaCXX/cxx26-trivial-union.cpp diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def index 7e6e2147a448d..0fd4a86de6f5a 100644 --- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def +++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def @@ -119,6 +119,11 @@ FIELD(HasInitMethod, 1, NO_MERGE) /// within anonymous unions or structs. FIELD(HasInClassInitializer, 1, NO_MERGE) +/// True if any field with an in-class initializer has a type (or base +/// element type) with a non-trivial destructor. Used for C++26 union +/// destructor deletion check ([class.dtor]p7 bullet 2). +FIELD(HasDMIWithNonTrivialDtor, 1, NO_MERGE) + /// True if any field is of reference type, and does not have an /// in-class initializer. /// diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 2af396f025c93..912691683fcb7 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1147,6 +1147,13 @@ class CXXRecordDecl : public RecordDecl { /// structs). bool hasInClassInitializer() const { return data().HasInClassInitializer; } + /// Whether this class has any in-class initializer for a non-static data + /// member whose type (or base element type) has a non-trivial destructor. + /// Used for C++26 union destructor deletion ([class.dtor]p7 bullet 2). + bool hasDMIWithNonTrivialDtor() const { + return data().HasDMIWithNonTrivialDtor; + } + /// Whether this class or any of its subobjects has any members of /// reference type which would make value-initialization ill-formed. /// diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6d2fae551566f..a6f9a4d92f511 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6429,6 +6429,12 @@ def note_enforce_read_only_placement : Note<"type was declared read-only here">; def note_deleted_dtor_no_operator_delete : Note< "virtual destructor requires an unambiguous, accessible 'operator delete'">; +def note_deleted_dtor_default_ctor : Note< + "destructor of union %0 is implicitly deleted because " + "%select{" + "%select{it has no default constructor|its default constructor is a deleted function}2|" + "overload resolution to default-initialize it is ambiguous|" + "its default constructor is not trivial}1">; def note_deleted_special_member_class_subobject : Note< "%select{default constructor of|copy constructor of|move constructor of|" "copy assignment operator of|move assignment operator of|destructor of|" diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 3b9d888bb2c0a..32ae2131beae1 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -81,6 +81,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasPrivateFields(false), HasProtectedFields(false), HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false), HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false), + HasDMIWithNonTrivialDtor(false), HasUninitializedReferenceMember(false), HasUninitializedFields(false), HasInheritedConstructor(false), HasInheritedDefaultConstructor(false), HasInheritedAssignment(false), @@ -1177,10 +1178,29 @@ void CXXRecordDecl::addedMember(Decl *D) { Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) { data().HasInClassInitializer = true; + // C++26 [class.dtor]p7 bullet 2: track whether any field with a DMI + // has a type with a non-trivial destructor, for union dtor deletion. + if (Field->hasInClassInitializer()) { + QualType FT = + getASTContext().getBaseElementType(Field->getType()); + if (const auto *FR = FT->getAsCXXRecordDecl(); + FR && FR->hasNonTrivialDestructor()) + data().HasDMIWithNonTrivialDtor = true; + } else if (Field->isAnonymousStructOrUnion()) { + if (Field->getType()->getAsCXXRecordDecl()->data() + .HasDMIWithNonTrivialDtor) + data().HasDMIWithNonTrivialDtor = true; + } + // C++11 [class]p5: // A default constructor is trivial if [...] no non-static data member // of its class has a brace-or-equal-initializer. - data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; + // C++26 [class.default.ctor]p3: + // A default constructor is trivial if [...] either X is a union or + // no non-static data member of its class has a + // brace-or-equal-initializer. + if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26)) + data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++11 [dcl.init.aggr]p1: // An aggregate is a [...] class with [...] no @@ -1239,7 +1259,11 @@ void CXXRecordDecl::addedMember(Decl *D) { if (FieldRec->hasNonTrivialMoveAssignment()) data().DefaultedMoveAssignmentIsDeleted = true; if (FieldRec->hasNonTrivialDestructor()) { - data().DefaultedDestructorIsDeleted = true; + // In C++26, the destructor of a union is not deleted merely + // because a variant member has a non-trivial destructor. + // Deletion is determined later by Sema (see C++26 [class.dtor]p7). + if (!Context.getLangOpts().CPlusPlus26) + data().DefaultedDestructorIsDeleted = true; // C++20 [dcl.constexpr]p5: // The definition of a constexpr destructor whose function-body is // not = delete shall additionally satisfy... @@ -1267,7 +1291,13 @@ 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()) + // C++26 [class.default.ctor]p3: + // A default constructor is trivial if [...] either X is a union + // or for all the non-static data members of its class that are + // of class type [...] each such class has a trivial default + // constructor. + if (!FieldRec->hasTrivialDefaultConstructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++0x [class.copy]p13: @@ -1305,9 +1335,15 @@ void CXXRecordDecl::addedMember(Decl *D) { if (!FieldRec->hasTrivialMoveAssignment()) data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment; - if (!FieldRec->hasTrivialDestructor()) + // C++26 [class.dtor]p8: + // A destructor is trivial if [...] either X is a union or for + // all the non-static data members of its class that are of class + // type [...] each such class has a trivial destructor. + if (!FieldRec->hasTrivialDestructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_Destructor; - if (!FieldRec->hasTrivialDestructorForCall()) + if (!FieldRec->hasTrivialDestructorForCall() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; if (!FieldRec->hasIrrelevantDestructor()) data().HasIrrelevantDestructor = false; diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1ccd74314f373..a00b34625510b 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -746,6 +746,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_variadic_friend", "202403L"); Builder.defineMacro("__cpp_trivial_relocatability", "202502L"); + // C++26 features. + if (LangOpts.CPlusPlus26) + Builder.defineMacro("__cpp_trivial_union", "202502L"); + if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c1d3960e65ef6..50659188c4244 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9631,6 +9631,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) { if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field && Field->getParent()->isUnion()) { + // In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor (see C++26 [class.default.ctor]p3). + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9653,6 +9658,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( // destructor is never actually called, but is semantically checked as // if it were. if (CSM == CXXSpecialMemberKind::DefaultConstructor) { + // In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor (see C++26 [class.default.ctor]p3). + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9661,6 +9671,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( const auto *RD = cast<CXXRecordDecl>(Field->getParent()); if (!RD->hasInClassInitializer()) DiagKind = NonTrivialDecl; + } else if (CSM == CXXSpecialMemberKind::Destructor && + S.getLangOpts().CPlusPlus26) { + // In C++26, a union's destructor is not deleted merely because a + // variant member has a non-trivial destructor. Deletion is determined + // by the new union-specific rules (see C++26 [class.dtor]p7). + return false; } else { DiagKind = NonTrivialDecl; } @@ -9822,6 +9838,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD)) return true; + // In C++26, a union's defaulted default constructor is trivially defined + // and never deleted due to variant member properties + // (see C++26 [class.default.ctor]p3). + if (inUnion() && S.getLangOpts().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. @@ -9900,7 +9923,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.getLangOpts().CPlusPlus26) { if (Diagnose) S.Diag(FieldRecord->getLocation(), diag::note_deleted_default_ctor_all_const) @@ -9930,6 +9954,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() { // default constructor. Don't do that. if (CSM == CXXSpecialMemberKind::DefaultConstructor && inUnion() && AllFieldsAreConst) { + if (S.getLangOpts().CPlusPlus26) + return false; bool AnyFields = false; for (auto *F : MD->getParent()->fields()) if ((AnyFields = !F->isUnnamedBitField())) @@ -9945,6 +9971,72 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() { return false; } +/// C++26 [class.dtor]p7: Check union-specific destructor deletion rules. +/// Returns true if the destructor should be deleted. +static bool ShouldDeleteUnionDestructorInCpp26(Sema &S, CXXRecordDecl *RD, + CXXMethodDecl *MD, + bool Diagnose) { + // Bullet 1: overload resolution for default initialization. + Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember( + RD, CXXSpecialMemberKind::DefaultConstructor, + /*ConstArg=*/false, /*VolatileArg=*/false, /*RValueThis=*/false, + /*ConstThis=*/false, /*VolatileThis=*/false); + bool CtorOK = false; + if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Success) { + auto *Ctor = cast<CXXConstructorDecl>(SMOR.getMethod()); + // Use !isUserProvided() rather than isTrivial() because the triviality + // flag may not be set yet at the point DeclareImplicitDestructor runs. + CtorOK = !Ctor->isDeleted() && !Ctor->isUserProvided(); + } else if (SMOR.getKind() == + Sema::SpecialMemberOverloadResult::NoMemberOrDeleted && + !SMOR.getMethod()) { + CtorOK = true; + } + if (!CtorOK) { + if (Diagnose) { + auto *Ctor = SMOR.getMethod(); + S.Diag(RD->getLocation(), diag::note_deleted_dtor_default_ctor) + << RD << SMOR.getKind() << (Ctor && Ctor->isDeleted()); + } + return true; + } + + // Bullet 2: variant member with DMI and non-trivial dtor. + if (!RD->hasDMIWithNonTrivialDtor()) + return false; + + if (!Diagnose) + return true; + + // Walk fields to find the specific field for the diagnostic. + auto FindDMIWithNonTrivialDtor = [&](const CXXRecordDecl *Record, + auto &Self) -> bool { + for (const FieldDecl *FD : Record->fields()) { + if (FD->hasInClassInitializer()) { + QualType FT = S.Context.getBaseElementType(FD->getType()); + if (const auto *FR = FT->getAsCXXRecordDecl(); + FR && FR->hasNonTrivialDestructor()) { + S.Diag(FD->getLocation(), + diag::note_deleted_special_member_class_subobject) + << S.getSpecialMember(MD) << RD << /*IsField=*/true << FD + << /*NonTrivialDecl=*/4 << /*IsDtorCallInCtor=*/false + << /*IsObjCPtr=*/false; + return true; + } + } + if (FD->isAnonymousStructOrUnion()) { + if (const auto *SubRD = FD->getType()->getAsCXXRecordDecl()) { + if (Self(SubRD, Self)) + return true; + } + } + } + return false; + }; + FindDMIWithNonTrivialDtor(RD, FindDMIWithNonTrivialDtor); + return true; +} + /// Determine whether a defaulted special member function should be defined as /// deleted, as specified in C++11 [class.ctor]p5, C++11 [class.copy]p11, /// C++11 [class.copy]p23, and C++11 [class.dtor]p5. @@ -10050,6 +10142,13 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + // C++26 [class.dtor]p7: union-specific destructor deletion rules. + if (getLangOpts().CPlusPlus26 && RD->isUnion() && + CSM == CXXSpecialMemberKind::Destructor) { + if (ShouldDeleteUnionDestructorInCpp26(*this, RD, MD, Diagnose)) + return true; + } + // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. // Per DR1658, do not consider virtual bases of destructors of abstract @@ -10483,7 +10582,16 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM, // -- for all of the non-static data members of its class that are of class // type (or array thereof), each such class has a trivial [default // constructor or destructor] - if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) + // + // C++26 [class.default.ctor]p3, [class.dtor]p8: + // A default constructor [destructor] is trivial if [...] either X is + // a union or for all the non-static data members [...] each such class + // has a trivial default constructor [destructor]. + if (RD->isUnion() && getLangOpts().CPlusPlus26 && + (CSM == CXXSpecialMemberKind::DefaultConstructor || + CSM == CXXSpecialMemberKind::Destructor)) { + // Skip member triviality checks for unions in C++26. + } else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) return false; // C++11 [class.dtor]p5: diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index afa4bc9d8179a..4db8e38d9a5e5 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -1,18 +1,18 @@ // RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected -// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11 -// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 -// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 -// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 +// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26 +// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 +// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 +// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 // RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected -// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11 -// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 -// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 -// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 +// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26 +// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 +// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 +// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 namespace cwg1413 { // cwg1413: 12 template<int> struct Check { @@ -128,7 +128,7 @@ struct A { namespace cwg1460 { // cwg1460: 3.5 #if __cplusplus >= 201103L namespace DRExample { - union A { + union A { // #cwg1460-DRExample-A union {}; // since-cxx11-error@-1 {{declaration does not declare anything}} union {}; @@ -136,6 +136,8 @@ namespace cwg1460 { // cwg1460: 3.5 constexpr A() {} }; constexpr A a = A(); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-DRExample-A {{destructor of union 'A' is implicitly deleted because its default constructor is not trivial}} union B { union {}; @@ -277,7 +279,7 @@ namespace cwg1460 { // cwg1460: 3.5 }; static_assert(A().a == 1 && A().b == 2 && A().c == 3, ""); - union B { + union B { // #cwg1460-Overriding-B int a, b = 2, c; constexpr B() : a(1) {} constexpr B(char) : b(4) {} @@ -285,24 +287,42 @@ namespace cwg1460 { // cwg1460: 3.5 constexpr B(const char*) {} }; static_assert(B().a == 1, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B().b == 2, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B('x').a == 0, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B('x').b == 4, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B(123).b == 2, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B(123).c == 3, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").a == 1, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").b == 2, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").c == 3, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} struct C { union { int a, b = 2, c; }; diff --git a/clang/test/CXX/special/class.ctor/p5-0x.cpp b/clang/test/CXX/special/class.ctor/p5-0x.cpp index e0c53058f892b..f00ec4ddc6350 100644 --- a/clang/test/CXX/special/class.ctor/p5-0x.cpp +++ b/clang/test/CXX/special/class.ctor/p5-0x.cpp @@ -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; }; @@ -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; }; @@ -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; diff --git a/clang/test/CXX/special/class.ctor/p6-0x.cpp b/clang/test/CXX/special/class.ctor/p6-0x.cpp index 156a2b20c6b52..474e7aad91207 100644 --- a/clang/test/CXX/special/class.ctor/p6-0x.cpp +++ b/clang/test/CXX/special/class.ctor/p6-0x.cpp @@ -1,11 +1,12 @@ -// 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. -struct NonConstexpr1 { // expected-note {{here}} +struct NonConstexpr1 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}} int a; }; -struct NonConstexpr2 { // expected-note {{here}} +struct NonConstexpr2 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}} NonConstexpr1 nl; }; struct NonConstexpr2a : NonConstexpr1 { }; @@ -15,8 +16,8 @@ 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}} + friend constexpr NonConstexpr2::NonConstexpr2(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}} }; struct Constexpr1 {}; @@ -31,14 +32,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; @@ -53,11 +54,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; }; @@ -79,7 +80,7 @@ namespace UnionCtors { int d = 5; }; }; - struct E { // expected-note {{here}} + struct E { // cxx11-23-note {{here}} union { int a; int b; @@ -87,11 +88,11 @@ namespace UnionCtors { }; 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}} }; } @@ -122,6 +123,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}} } diff --git a/clang/test/CXX/special/class.dtor/p5-0x.cpp b/clang/test/CXX/special/class.dtor/p5-0x.cpp index ae14dcdaf102a..286e980710009 100644 --- a/clang/test/CXX/special/class.dtor/p5-0x.cpp +++ b/clang/test/CXX/special/class.dtor/p5-0x.cpp @@ -1,10 +1,11 @@ -// RUN: %clang_cc1 -verify -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu +// RUN: %clang_cc1 -verify=expected,precxx26 -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu +// RUN: %clang_cc1 -verify=expected,cxx26 -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu struct NonTrivDtor { ~NonTrivDtor(); }; struct DeletedDtor { - ~DeletedDtor() = delete; // expected-note 5 {{deleted here}} + ~DeletedDtor() = delete; // expected-note 4 {{deleted here}} precxx26-note {{deleted here}} }; class InaccessibleDtor { ~InaccessibleDtor() = default; @@ -14,30 +15,30 @@ class InaccessibleDtor { // -- X is a union-like class that has a variant member with a non-trivial // destructor. -union A1 { +union A1 { // cxx26-note {{not trivial}} A1(); - NonTrivDtor n; // expected-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n; // precxx26-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}} }; A1 a1; // expected-error {{deleted function}} struct A2 { A2(); union { - NonTrivDtor n; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; }; -A2 a2; // expected-error {{deleted function}} -union A3 { +A2 a2; // precxx26-error {{deleted function}} +union A3 { // cxx26-note {{not trivial}} A3(); - NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; A3 a3; // expected-error {{deleted function}} struct A4 { A4(); union { - NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; }; -A4 a4; // expected-error {{deleted function}} +A4 a4; // precxx26-error {{deleted function}} // -- any of the non-static data members has class type M (or array thereof) and // M has a deleted or inaccessible destructor. @@ -61,17 +62,17 @@ struct B4 { InaccessibleDtor a[4]; // expected-note {{because field 'a' has an inaccessible destructor}} }; B4 b4; // expected-error {{deleted function}} -union B5 { +union B5 { // cxx26-note {{not trivial}} B5(); - union { // expected-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}} - DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}} + union { // precxx26-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}} + DeletedDtor a; // precxx26-note {{because field 'a' has a deleted destructor}} }; }; B5 b5; // expected-error {{deleted function}} -union B6 { +union B6 { // cxx26-note {{not trivial}} B6(); - union { // expected-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}} - InaccessibleDtor a; // expected-note {{because field 'a' has an inaccessible destructor}} + union { // precxx26-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}} + InaccessibleDtor a; // precxx26-note {{because field 'a' has an inaccessible destructor}} }; }; B6 b6; // expected-error {{deleted function}} diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp new file mode 100644 index 0000000000000..8265c4f4d7670 --- /dev/null +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -0,0 +1,253 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=cxx26 %s +// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=precxx26 %s + +// P3074R7: trivial unions + +struct NonTrivial { + NonTrivial(); + NonTrivial(const NonTrivial&); + NonTrivial& operator=(const NonTrivial&); + ~NonTrivial(); +}; + +struct NonTrivialDtor { + ~NonTrivialDtor(); +}; + +// ===== Test 1: Basic union with non-trivial member ===== +// P3074: default ctor and dtor should be trivial, not deleted. +union U1 { + NonTrivial nt; // precxx26-note 2{{non-trivial}} +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U1)); +static_assert(__is_trivially_destructible(U1)); +U1 test_u1; +#else +U1 test_u1_pre; // precxx26-error {{deleted}} +void destroy_u1(U1 *p) { p->~U1(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 2: Union with non-trivial member and int ===== +union U2 { + NonTrivial nt; + int k; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U2)); +static_assert(__is_trivially_destructible(U2)); +#endif + +// ===== Test 3: Union with DMI on member with non-trivial dtor ===== +// P3074: dtor is deleted because DMI + non-trivial dtor on same member. +union U3_deleted_dtor { + NonTrivialDtor ntd = {}; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_u3_destroy(U3_deleted_dtor *p) { + p->~U3_deleted_dtor(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 4: Union with DMI on non-class member ===== +// DMI on int, but NonTrivial has no DMI => dtor should NOT be deleted. +union U4 { + NonTrivial nt; // precxx26-note {{non-trivial}} + int k = 42; +}; + +#if __cplusplus > 202302L +// Despite non-trivial default ctor (due to DMI), destructor is NOT deleted +// because the member with DMI (k) is int (trivially destructible). +static_assert(__is_trivially_destructible(U4)); +#else +void destroy_u4(U4 *p) { p->~U4(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 5: Union with user-provided default constructor ===== +// Dtor is deleted: user-provided default ctor is non-trivial +// (see C++26 [class.dtor]p7). +union U5 { // cxx26-note {{not trivial}} + U5() : nt() {} + NonTrivialDtor nt; // precxx26-note {{non-trivial}} +}; + +void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} precxx26-error {{deleted}} + +// ===== Test 6: Feature test macro ===== +#if __cplusplus > 202302L +static_assert(__cpp_trivial_union >= 202502L); +#else +#ifdef __cpp_trivial_union +#error "should not have __cpp_trivial_union in C++23" +#endif +#endif + +// ===== Test 7: Trivial union (no change from status quo) ===== +union U7 { + int a; + float b; +}; + +static_assert(__is_trivially_constructible(U7)); +static_assert(__is_trivially_destructible(U7)); + +// ===== Test 8: Array member in union ===== +union U8 { + NonTrivial arr[4]; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U8)); +static_assert(__is_trivially_destructible(U8)); +#endif + +// ===== Test 9: Paper example - string with DMI ===== +struct FakeString { + FakeString(const char*); + FakeString(const FakeString&); + FakeString& operator=(const FakeString&); + ~FakeString(); +}; + +union PaperU2 { + FakeString s = "hello"; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_paper_u2(PaperU2 *p) { + p->~PaperU2(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 10: Paper example U4 - DMI on pointer, non-trivial string ===== +union PaperU4 { + FakeString s; // precxx26-note {{non-trivial}} + PaperU4 *next = nullptr; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_destructible(PaperU4)); +#else +void destroy_paper_u4(PaperU4 *p) { p->~PaperU4(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 11: No default ctor (suppressed by user-declared ctor) ===== +// When the implicit default ctor is suppressed (not deleted), the +// [class.dtor]p7 bullet 1 rule does not apply. The union can't be +// default-initialized, so no risk of indeterminate active member. +// Destructor is trivial (see C++26 [class.dtor]p7). +#if __cplusplus > 202302L +union U11 { + U11(int); + NonTrivialDtor nt; +}; +static_assert(__is_trivially_destructible(U11)); +U11 u11(1); + +// ===== Test 12: Deleted default ctor (dtor deleted per [class.dtor]p7) ===== +union U12 { // cxx26-note {{deleted function}} + U12() = delete; + U12(int); + NonTrivialDtor nt; +}; +U12 u12(1); // cxx26-error {{deleted}} + +// ===== Test 13: Defaulted ctor => trivial, dtor NOT deleted ===== +union U13 { + U13() = default; + NonTrivialDtor nt; + U13 *next = nullptr; +}; +U13 u13; + +// ===== Test 14: Array member with DMI + non-trivial dtor ([class.dtor]p7 bullet 2) ===== +struct NonTrivialInt { + int i; + constexpr NonTrivialInt(int i) : i(i) {} + constexpr ~NonTrivialInt() {} +}; + +union U14 { + NonTrivialInt arr[2] = {1, 2}; // cxx26-note {{non-trivial}} +}; +U14 u14; // cxx26-error {{deleted}} + +// ===== Test 15: Anonymous struct in union, no DMI => NOT deleted ===== +union U15 { + struct { + NonTrivialDtor x; + }; +}; +U15 u15; + +// ===== Test 16: Anonymous struct in union, with DMI => deleted ===== +union U16 { + struct { + NonTrivialInt x = 1; // cxx26-note {{non-trivial}} + }; +}; +U16 u16; // cxx26-error {{deleted}} + +// ===== Test 17: struct containing anonymous union ===== +struct S17 { + union { + NonTrivialDtor x; + }; +}; +S17 s17; + +// ===== Test 18: Deeply nested union-struct-union-struct-union ===== +union U18 { + struct { + union { + struct { + union { + NonTrivialDtor x; + }; + }; + }; + }; +}; +U18 u18; + +// ===== Test 19: struct-union-struct-union-struct nesting ===== +struct S19 { + union { + struct { + union { + struct { + NonTrivialDtor x; + }; + }; + }; + }; +}; +S19 s19; + +// ===== Test 20: Anonymous union inside union ===== +union U20 { + union { + NonTrivialDtor x; + }; +}; +U20 u20; + +// ===== Test 21: Deleted destructor member (p7.2, not union-specific) ===== +// p7.2 still applies: deleted/inaccessible member dtor => union dtor deleted. +struct DeletedDtor { + ~DeletedDtor() = delete; // cxx26-note {{deleted here}} precxx26-note {{deleted here}} +}; + +union U21 { + DeletedDtor a; // cxx26-note {{deleted destructor}} precxx26-note {{deleted destructor}} +}; +void test_u21(U21 *p) { p->~U21(); } // cxx26-error {{deleted}} precxx26-error {{deleted}} + +// ===== Test 22: Constexpr evaluation of trivial union ===== +constexpr int constexpr_test() { + U2 u; + u.k = 42; + return u.k; +} +static_assert(constexpr_test() == 42); +#endif >From 3faefdb97098efd7614510c1bb2ba9d642a86a79 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Fri, 10 Apr 2026 15:33:40 +0800 Subject: [PATCH 2/6] Address P3074 review feedback --- clang/lib/AST/DeclCXX.cpp | 19 ++++++++++--------- clang/lib/Sema/SemaDeclCXX.cpp | 15 +++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 32ae2131beae1..91d9dc49c362a 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -81,10 +81,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasPrivateFields(false), HasProtectedFields(false), HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false), HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false), - HasDMIWithNonTrivialDtor(false), - HasUninitializedReferenceMember(false), HasUninitializedFields(false), - HasInheritedConstructor(false), HasInheritedDefaultConstructor(false), - HasInheritedAssignment(false), + HasDMIWithNonTrivialDtor(false), HasUninitializedReferenceMember(false), + HasUninitializedFields(false), HasInheritedConstructor(false), + HasInheritedDefaultConstructor(false), HasInheritedAssignment(false), NeedOverloadResolutionForCopyConstructor(false), NeedOverloadResolutionForMoveConstructor(false), NeedOverloadResolutionForCopyAssignment(false), @@ -1178,16 +1177,18 @@ void CXXRecordDecl::addedMember(Decl *D) { Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) { data().HasInClassInitializer = true; - // C++26 [class.dtor]p7 bullet 2: track whether any field with a DMI - // has a type with a non-trivial destructor, for union dtor deletion. + // C++26 [class.dtor]p7 bullet 2: track whether any field with a + // default member initializer (DMI) has a type with a non-trivial + // destructor, for union destructor deletion. if (Field->hasInClassInitializer()) { - QualType FT = - getASTContext().getBaseElementType(Field->getType()); + QualType FT = getASTContext().getBaseElementType(Field->getType()); if (const auto *FR = FT->getAsCXXRecordDecl(); FR && FR->hasNonTrivialDestructor()) data().HasDMIWithNonTrivialDtor = true; } else if (Field->isAnonymousStructOrUnion()) { - if (Field->getType()->getAsCXXRecordDecl()->data() + if (Field->getType() + ->getAsCXXRecordDecl() + ->data() .HasDMIWithNonTrivialDtor) data().HasDMIWithNonTrivialDtor = true; } diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 50659188c4244..5adddbcc3e28d 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9673,9 +9673,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( DiagKind = NonTrivialDecl; } else if (CSM == CXXSpecialMemberKind::Destructor && S.getLangOpts().CPlusPlus26) { - // In C++26, a union's destructor is not deleted merely because a - // variant member has a non-trivial destructor. Deletion is determined - // by the new union-specific rules (see C++26 [class.dtor]p7). + // C++26 [class.dtor]p7: union destructor deletion is determined + // by the union-specific rules, not variant member triviality. return false; } else { DiagKind = NonTrivialDecl; @@ -9838,9 +9837,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD)) return true; - // In C++26, a union's defaulted default constructor is trivially defined - // and never deleted due to variant member properties - // (see C++26 [class.default.ctor]p3). + // C++26 [class.default.ctor]p3: a union's defaulted default constructor + // is trivially defined regardless of variant members. if (inUnion() && S.getLangOpts().CPlusPlus26 && CSM == CXXSpecialMemberKind::DefaultConstructor) return false; @@ -10001,7 +9999,8 @@ static bool ShouldDeleteUnionDestructorInCpp26(Sema &S, CXXRecordDecl *RD, return true; } - // Bullet 2: variant member with DMI and non-trivial dtor. + // Bullet 2: variant member with default member initializer (DMI) + // and non-trivial destructor. if (!RD->hasDMIWithNonTrivialDtor()) return false; @@ -10590,7 +10589,7 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM, if (RD->isUnion() && getLangOpts().CPlusPlus26 && (CSM == CXXSpecialMemberKind::DefaultConstructor || CSM == CXXSpecialMemberKind::Destructor)) { - // Skip member triviality checks for unions in C++26. + // Union member triviality doesn't affect the union's own triviality. } else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) return false; >From 09fed545c9136bf6fd7a7f9a6000b5aa32d6634f Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Thu, 16 Apr 2026 15:42:09 +0800 Subject: [PATCH 3/6] Update release notes and cxx_status for P3074R7 --- clang/docs/ReleaseNotes.rst | 3 +++ clang/www/cxx_status.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 2da7175b51ea3..58d3487707902 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -127,6 +127,9 @@ C++ Language Changes C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ +- Implemented `P3074R7: Trivial unions <https://wg21.link/P3074R7>`_ + (``__cpp_trivial_union``). + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 2c834b07f9a8f..72c62f8424585 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -259,7 +259,7 @@ <h2 id="cxx26">C++2c implementation status</h2> <tr> <td>Trivial unions</td> <td><a href="https://wg21.link/P3074">P3074R7</a></td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 23</td> </tr> <tr> <td>Partial program correctness</td> >From 5227113bf1beb9987328237f3bb803f153fb4b33 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Fri, 17 Apr 2026 14:24:29 +0800 Subject: [PATCH 4/6] Address P3074 review feedback (round 2) --- clang/lib/Sema/SemaDeclCXX.cpp | 17 ++++++++------- clang/test/CXX/drs/cwg14xx.cpp | 40 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 5adddbcc3e28d..f6c1b5cc0930b 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9971,9 +9971,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() { /// C++26 [class.dtor]p7: Check union-specific destructor deletion rules. /// Returns true if the destructor should be deleted. -static bool ShouldDeleteUnionDestructorInCpp26(Sema &S, CXXRecordDecl *RD, - CXXMethodDecl *MD, - bool Diagnose) { +static bool ShouldDeleteNonTrivialUnionDestructor(Sema &S, CXXRecordDecl *RD, + CXXMethodDecl *MD, + bool Diagnose) { + if (!S.getLangOpts().CPlusPlus26 || !RD->isUnion()) + return false; + // Bullet 1: overload resolution for default initialization. Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember( RD, CXXSpecialMemberKind::DefaultConstructor, @@ -10142,11 +10145,9 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); // C++26 [class.dtor]p7: union-specific destructor deletion rules. - if (getLangOpts().CPlusPlus26 && RD->isUnion() && - CSM == CXXSpecialMemberKind::Destructor) { - if (ShouldDeleteUnionDestructorInCpp26(*this, RD, MD, Diagnose)) - return true; - } + if (CSM == CXXSpecialMemberKind::Destructor && + ShouldDeleteNonTrivialUnionDestructor(*this, RD, MD, Diagnose)) + return true; // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index 4db8e38d9a5e5..f804fb70ccd98 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -1,17 +1,17 @@ // RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected -// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26 -// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11,cxx11-23 +// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,cxx11-23 +// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,cxx11-23 +// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx11-23 +// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx11-23 // RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 // RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected -// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26 -// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 +// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11,cxx11-23 +// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,cxx11-23 +// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,cxx11-23 +// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx11-23 +// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx11-23 // RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 namespace cwg1413 { // cwg1413: 12 @@ -290,37 +290,37 @@ namespace cwg1460 { // cwg1460: 3.5 // since-cxx26-error@-1 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B().b == 2, ""); - // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} - // precxx26-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} + // cxx11-23-error@-1 {{static assertion expression is not an integral constant expression}} + // cxx11-23-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} // since-cxx26-error@-3 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B('x').a == 0, ""); - // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} - // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // cxx11-23-error@-1 {{static assertion expression is not an integral constant expression}} + // cxx11-23-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} // since-cxx26-error@-3 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B('x').b == 4, ""); // since-cxx26-error@-1 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B(123).b == 2, ""); - // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} - // precxx26-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} + // cxx11-23-error@-1 {{static assertion expression is not an integral constant expression}} + // cxx11-23-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} // since-cxx26-error@-3 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B(123).c == 3, ""); // since-cxx26-error@-1 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").a == 1, ""); - // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} - // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // cxx11-23-error@-1 {{static assertion expression is not an integral constant expression}} + // cxx11-23-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} // since-cxx26-error@-3 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").b == 2, ""); // since-cxx26-error@-1 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B("").c == 3, ""); - // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} - // precxx26-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} + // cxx11-23-error@-1 {{static assertion expression is not an integral constant expression}} + // cxx11-23-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} // since-cxx26-error@-3 {{attempt to use a deleted function}} // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} >From d7fed4233a3d3a63e826d4cce92037aaba189638 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 13 May 2026 10:54:46 +0800 Subject: [PATCH 5/6] Add test for ambiguous default ctor diagnostic in trivial union --- clang/test/SemaCXX/cxx26-trivial-union.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp index 8265c4f4d7670..e2f3121479ad0 100644 --- a/clang/test/SemaCXX/cxx26-trivial-union.cpp +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -250,4 +250,14 @@ constexpr int constexpr_test() { return u.k; } static_assert(constexpr_test() == 42); + +// ===== Test 23: Ambiguous default ctor => dtor deleted ===== +// Multiple constructors with default arguments make default-initialization +// ambiguous, so dtor is deleted per [class.dtor]p7 bullet 1. +union U23 { // cxx26-note {{ambiguous}} + U23(int = 0); + U23(double = 0.0); + NonTrivialDtor nt; +}; +U23 u23(42); // cxx26-error {{deleted}} #endif >From 584a94d205b5429fbc3d42bca79b789c49c3d737 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 13 May 2026 16:46:54 +0800 Subject: [PATCH 6/6] Add ptrauth union triviality tests for P3074 --- clang/test/SemaCXX/ptrauth-triviality.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clang/test/SemaCXX/ptrauth-triviality.cpp b/clang/test/SemaCXX/ptrauth-triviality.cpp index 900c5293bcd3b..712dab2a4e025 100644 --- a/clang/test/SemaCXX/ptrauth-triviality.cpp +++ b/clang/test/SemaCXX/ptrauth-triviality.cpp @@ -192,3 +192,20 @@ template <class... Types> static const bool inheritance_relocatability_matches_b static_assert(multiple_inheritance_is_relocatable<S4, S5> == multiple_inheritance_is_relocatable<S5, S4>); static_assert(inheritance_relocatability_matches_bases_v<S4, S5>); static_assert(inheritance_relocatability_matches_bases_v<S5, S4>); + +// P3074: ptrauth-qualified pointer members in unions. +// These should be trivially constructible and destructible — they're just +// unions of pointers regardless of signing scheme. +union PtrauthCanBeIrksome { + int *p; + int *__ptrauth(1,0,1) ptrauth_forever; +}; +static_assert(__is_trivially_constructible(PtrauthCanBeIrksome)); +static_assert(__is_trivially_destructible(PtrauthCanBeIrksome)); + +union PtrauthCanBeIrksomeEspWithAddressDiscrimination { + int *p; + int *__ptrauth(1,1,1) ptrauth_forever; +}; +static_assert(__is_trivially_constructible(PtrauthCanBeIrksomeEspWithAddressDiscrimination)); +static_assert(__is_trivially_destructible(PtrauthCanBeIrksomeEspWithAddressDiscrimination)); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
