llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Peng Xie (love1angel) <details> <summary>Changes</summary> 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). Close #<!-- -->127868 Related #<!-- -->146815 ping @<!-- -->brevzin --- Patch is 40.28 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/185886.diff 9 Files Affected: - (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6) - (modified) clang/lib/AST/DeclCXX.cpp (+24-5) - (modified) clang/lib/Frontend/InitPreprocessor.cpp (+4) - (modified) clang/lib/Sema/SemaDeclCXX.cpp (+133-2) - (modified) clang/test/CXX/drs/cwg14xx.cpp (+36-24) - (modified) clang/test/CXX/special/class.ctor/p5-0x.cpp (+9-7) - (modified) clang/test/CXX/special/class.ctor/p6-0x.cpp (+16-15) - (modified) clang/test/CXX/special/class.dtor/p5-0x.cpp (+17-16) - (added) clang/test/SemaCXX/cxx26-trivial-union.cpp (+251) ``````````diff diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0c25eb2443d5e..0cb33bc9511c3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6406,6 +6406,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{it has no default constructor|" + "its default constructor is a deleted function|" + "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 083c53e28cb91..6cb90e701fff4 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1180,7 +1180,11 @@ void CXXRecordDecl::addedMember(Decl *D) { // 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; + // P3074R7 [class.default.ctor]p3: + // In C++26, a union's default constructor is always trivial, + // even with brace-or-equal-initializers. + if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26)) + data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++11 [dcl.init.aggr]p1: // An aggregate is a [...] class with [...] no @@ -1239,7 +1243,11 @@ void CXXRecordDecl::addedMember(Decl *D) { if (FieldRec->hasNonTrivialMoveAssignment()) data().DefaultedMoveAssignmentIsDeleted = true; if (FieldRec->hasNonTrivialDestructor()) { - data().DefaultedDestructorIsDeleted = true; + // P3074R7: 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 based on the new rules. + 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 +1275,12 @@ 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()) + // P3074R7 [class.default.ctor]p3: + // In C++26, "either X is a union or" for all non-variant + // non-static data members [...] 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 +1318,15 @@ void CXXRecordDecl::addedMember(Decl *D) { if (!FieldRec->hasTrivialMoveAssignment()) data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment; - if (!FieldRec->hasTrivialDestructor()) + // P3074R7 [class.dtor]p8: + // In C++26, "either X is a union or" for all non-variant + // non-static data members [...] 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 2ae6e5de0e3ee..c316eca5fc72e 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9615,6 +9615,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) { if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field && Field->getParent()->isUnion()) { + // P3074R7: In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor. The old [class.default.ctor]p2 union-specific bullets + // are removed. + 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 @@ -9637,6 +9643,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( // destructor is never actually called, but is semantically checked as // if it were. if (CSM == CXXSpecialMemberKind::DefaultConstructor) { + // P3074R7: In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor. + 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 @@ -9645,6 +9656,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( const auto *RD = cast<CXXRecordDecl>(Field->getParent()); if (!RD->hasInClassInitializer()) DiagKind = NonTrivialDecl; + } else if (CSM == CXXSpecialMemberKind::Destructor && + S.getLangOpts().CPlusPlus26) { + // P3074R7 [class.dtor]p7: 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 in + // ShouldDeleteSpecialMember. + return false; } else { DiagKind = NonTrivialDecl; } @@ -9806,6 +9824,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD)) return true; + // P3074R7: In C++26, a union's defaulted default constructor is trivially + // defined and never deleted due to variant member properties. + 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. @@ -9884,7 +9908,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) @@ -9914,6 +9939,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())) @@ -10034,6 +10061,100 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + // P3074R7 [class.dtor]p7.x: + // In C++26, a defaulted destructor for a union X is defined as deleted if: + // (7.x.1) 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 + // (7.x.2) 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. + // Note: p7.2 (deleted/inaccessible member dtor) still applies to unions. + // We handle 7.x.1 and 7.x.2 here and fall through to the normal + // per-member visit for p7.2 checking. shouldDeleteForSubobjectCall + // already returns false for non-trivial-but-not-deleted dtors in C++26 + // unions, so only truly deleted/inaccessible dtors will cause deletion + // through the member visit. + if (getLangOpts().CPlusPlus26 && RD->isUnion() && + CSM == CXXSpecialMemberKind::Destructor) { + // Check (7.x.1): overload resolution for default initialization. + SpecialMemberOverloadResult SMOR = LookupSpecialMember( + RD, CXXSpecialMemberKind::DefaultConstructor, + /*ConstArg=*/false, /*VolatileArg=*/false, /*RValueThis=*/false, + /*ConstThis=*/false, /*VolatileThis=*/false); + bool CtorOK = false; + if (SMOR.getKind() == SpecialMemberOverloadResult::Success) { + auto *Ctor = cast<CXXConstructorDecl>(SMOR.getMethod()); + // In C++26, union default ctors are trivial unless user-provided + // (P3074R7 [class.default.ctor]p3). We use !isUserProvided() rather + // than isTrivial() because the triviality flag may not be set yet for + // explicitly defaulted ctors at the point DeclareImplicitDestructor + // runs during class completion. + CtorOK = !Ctor->isDeleted() && !Ctor->isUserProvided(); + } else if (SMOR.getKind() == + SpecialMemberOverloadResult::NoMemberOrDeleted) { + if (!SMOR.getMethod()) { + // No default constructor exists (e.g. suppressed by user-declared + // constructors, or no viable candidate for 0 args). The union + // cannot be default-initialized, so rule 7.x.1 does not apply. + CtorOK = true; + } + // else: a deleted default ctor was selected → CtorOK stays false. + } + // Ambiguous → CtorOK stays false. + if (!CtorOK) { + // 7.x.1: OR is ambiguous, selects a deleted ctor, or selects a + // non-trivial (user-provided) ctor. + if (Diagnose) { + unsigned Reason; + if (SMOR.getKind() == SpecialMemberOverloadResult::Ambiguous) + Reason = 2; // ambiguous + else if (SMOR.getKind() == + SpecialMemberOverloadResult::NoMemberOrDeleted) { + auto *Ctor = SMOR.getMethod(); + Reason = (Ctor && Ctor->isDeleted()) ? 1 : 0; + } else { + Reason = 3; // not trivial + } + Diag(RD->getLocation(), diag::note_deleted_dtor_default_ctor) + << RD << Reason; + } + return true; + } + // Ctor is OK. Check (7.x.2): walk variant members (including through + // anonymous structs) for DMI + non-trivial dtor. + std::function<bool(const CXXRecordDecl *)> HasDMINonTrivDtor = + [&](const CXXRecordDecl *Record) -> bool { + for (const auto *FD : Record->fields()) { + if (FD->hasInClassInitializer()) { + QualType FT = Context.getBaseElementType(FD->getType()); + if (const auto *FR = FT->getAsCXXRecordDecl()) { + if (FR->hasNonTrivialDestructor()) { + if (Diagnose) + Diag(FD->getLocation(), + diag::note_deleted_special_member_class_subobject) + << 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 (HasDMINonTrivDtor(SubRD)) + return true; + } + } + } + return false; + }; + if (HasDMINonTrivDtor(RD)) + return true; + // Neither 7.x.1 nor 7.x.2 triggered. Fall through to the normal + // per-member visit for p7.2 (deleted/inaccessible dtor) checking. + } + // 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 @@ -10467,7 +10588,17 @@ 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)) + // + // P3074R7 [class.default.ctor]p3, [class.dtor]p8: + // In C++26, "either X is a union or" the above member checks apply. + // For unions, default constructor and destructor are trivial regardless + // of member triviality. + if (RD->isUnion() && getLangOpts().CPlusPlus26 && + (CSM == CXXSpecialMemberKind::DefaultConstructor || + CSM == CXXSpecialMemberKind::Destructor)) { + // Union default ctor and destructor are trivial in C++26 per P3074. + // Skip member triviality checks. + } 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 dba1850ce8df9..fa9b866d6bfcd 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 -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors // RUN: %clang_cc1 -std=c++98 %s -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter 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,32 +279,42 @@ 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) {} constexpr B(int) : c(3) {} constexpr B(const char*) {} }; + // since-cxx26-note@#cwg1460-Overriding-B 9 {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B().a == 1, ""); + // since-cxx... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/185886 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
