diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -400,6 +400,11 @@ that can be used for such compatibility. The demangler now demangles symbols with named module attachment. +- As per "Conditionally Trivial Special Member Functions" (P0848), it is + now possible to overload destructors using concepts. Note that the + type traits support for trivial and trivially copyable types is not + yet implemented. + C++2b Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2251,6 +2251,13 @@ DeclAsWritten->getCanonicalDecl()->isDefaulted()); } + bool isEligibleOrSelected() const { + return FunctionDeclBits.IsEligibleOrSelected; + } + void setEligibleOrSelected(bool IE) { + FunctionDeclBits.IsEligibleOrSelected = IE; + } + /// Whether falling off this function implicitly returns null/zero. /// If a more specific implicit return value is required, front-ends /// should synthesize the appropriate return statements. diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1596,6 +1596,11 @@ uint64_t IsDefaulted : 1; uint64_t IsExplicitlyDefaulted : 1; uint64_t HasDefaultedFunctionInfo : 1; + + /// For complete types, whether a member function is an eligible special + /// member function or a selected destructor. See [class.mem.special]. + uint64_t IsEligibleOrSelected : 1; + uint64_t HasImplicitReturnZero : 1; uint64_t IsLateTemplateParsed : 1; @@ -1631,7 +1636,7 @@ }; /// Number of non-inherited bits in FunctionDeclBitfields. - enum { NumFunctionDeclBits = 27 }; + enum { NumFunctionDeclBits = 28 }; /// Stores the bits used by CXXConstructorDecl. If modified /// NumCXXConstructorDeclBits and the accessor @@ -1643,12 +1648,12 @@ /// For the bits in FunctionDeclBitfields. uint64_t : NumFunctionDeclBits; - /// 24 bits to fit in the remaining available space. + /// 23 bits to fit in the remaining available space. /// Note that this makes CXXConstructorDeclBitfields take /// exactly 64 bits and thus the width of NumCtorInitializers /// will need to be shrunk if some bit is added to NumDeclContextBitfields, /// NumFunctionDeclBitfields or CXXConstructorDeclBitfields. - uint64_t NumCtorInitializers : 21; + uint64_t NumCtorInitializers : 20; uint64_t IsInheritingConstructor : 1; /// Whether this constructor has a trail-allocated explicit specifier. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4715,6 +4715,10 @@ "reference to non-static member function must be called" "%select{|; did you mean to call it with no arguments?}0">; def note_possible_target_of_call : Note<"possible target for call">; +def err_no_viable_destructor : Error< + "no viable destructor found for class %0">; +def err_ambiguous_destructor : Error< + "destructor of class %0 is ambiguous">; def err_ovl_no_viable_object_call : Error< "no matching function for call to object of type %0">; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2954,6 +2954,7 @@ FunctionDeclBits.IsDefaulted = false; FunctionDeclBits.IsExplicitlyDefaulted = false; FunctionDeclBits.HasDefaultedFunctionInfo = false; + FunctionDeclBits.IsEligibleOrSelected = false; FunctionDeclBits.HasImplicitReturnZero = false; FunctionDeclBits.IsLateTemplateParsed = false; FunctionDeclBits.ConstexprKind = static_cast(ConstexprKind); diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1895,6 +1895,14 @@ DeclContext::lookup_result R = lookup(Name); + // If a destructor was marked as selected, return it. We don't always have + // a selected destructor: dependent types, unnamed structs. + for (auto* Decl : R) { + auto* DD = dyn_cast(Decl); + if (DD && DD->isEligibleOrSelected()) + return DD; + } + return R.empty() ? nullptr : dyn_cast(R.front()); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -17755,6 +17755,62 @@ AllIvarDecls.push_back(Ivar); } +namespace { +/// [class.dtor]p4: +/// At the end of the definition of a class, overload resolution is +/// performed among the prospective destructors declared in that class with +/// an empty argument list to select the destructor for the class, also +/// known as the selected destructor. +/// +/// We do the overload resolution here, then mark the selected constructor in the AST. +/// Later CXXRecordDecl::getDestructor() will return the selected constructor. +void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) { + if (!Record->hasUserDeclaredDestructor()) + return; + + SourceLocation Loc = Record->getLocation(); + OverloadCandidateSet OCS(Loc, OverloadCandidateSet::CSK_Normal); + + for (auto *Decl : Record->decls()) { + if (auto *DD = dyn_cast(Decl)) { + if (DD->isInvalidDecl()) + continue; + S.AddOverloadCandidate(DD, DeclAccessPair::make(DD, DD->getAccess()), {}, + OCS); + } + } + if (OCS.empty()) + return; + + OverloadCandidateSet::iterator Best; + unsigned Msg = 0; + OverloadCandidateDisplayKind DisplayKind; + + switch (OCS.BestViableFunction(S, Loc, Best)) { + case OR_Success: + case OR_Deleted: + Best->Function->setEligibleOrSelected(true); + break; + + case OR_Ambiguous: + Msg = diag::err_ambiguous_destructor; + DisplayKind = OCD_AmbiguousCandidates; + break; + + case OR_No_Viable_Function: + Msg = diag::err_no_viable_destructor; + DisplayKind = OCD_AllCandidates; + break; + } + + if (Msg) { + PartialDiagnostic Diag = S.PDiag(Msg) << Record; + OCS.NoteCandidates(PartialDiagnosticAt(Loc, Diag), S, DisplayKind, {}); + Record->setInvalidDecl(); + } +} +} // namespace + void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl, ArrayRef Fields, SourceLocation LBrac, SourceLocation RBrac, @@ -17781,6 +17837,9 @@ RecordDecl *Record = dyn_cast(EnclosingDecl); CXXRecordDecl *CXXRecord = dyn_cast(EnclosingDecl); + if (CXXRecord && !CXXRecord->isDependentType()) + ComputeSelectedDestructor(*this, CXXRecord); + // Start counting up the number of named members; make sure to include // members of anonymous structs and unions in the total. unsigned NumNamedMembers = 0; diff --git a/clang/test/CXX/class/class.dtor/p4.cpp b/clang/test/CXX/class/class.dtor/p4.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/class/class.dtor/p4.cpp @@ -0,0 +1,74 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s + +template +struct A { + ~A() = delete; // expected-note {{explicitly marked deleted}} + ~A() requires(N == 1) = delete; // expected-note {{explicitly marked deleted}} +}; + +template +struct B { + ~B() requires(N == 1) = delete; // expected-note {{explicitly marked deleted}} + virtual ~B() = delete; // expected-note {{explicitly marked deleted}} +}; + +template +concept CO1 = N == 1; + +template +concept CO2 = N > 0; + +template +struct C { + ~C() = delete; // expected-note {{explicitly marked deleted}} + ~C() requires(CO1) = delete; + ~C() requires(CO1 && CO2) = delete; // expected-note {{explicitly marked deleted}} +}; + +template +struct D { + ~D() requires(N != 0) = delete; // expected-note {{explicitly marked deleted}} + // expected-note@-1 {{candidate function has been explicitly deleted}} + // expected-note@-2 {{candidate function not viable: constraints not satisfied}} + // expected-note@-3 {{evaluated to false}} + ~D() requires(N == 1) = delete; + // expected-note@-1 {{candidate function has been explicitly deleted}} + // expected-note@-2 {{candidate function not viable: constraints not satisfied}} + // expected-note@-3 {{evaluated to false}} +}; + +template +concept Foo = requires (T t) { + { t.foo() }; +}; + +template +struct E { + void foo(); + ~E(); + ~E() requires Foo = delete; // expected-note {{explicitly marked deleted}} +}; + +template struct A<1>; +template struct A<2>; +template struct B<1>; +template struct B<2>; +template struct C<1>; +template struct C<2>; +template struct D<0>; // expected-error {{no viable destructor found for class 'D<0>'}} expected-note {{in instantiation of template}} +template struct D<1>; // expected-error {{destructor of class 'D<1>' is ambiguous}} expected-note {{in instantiation of template}} +template struct D<2>; +template struct E<1>; + +int main() { + A<1> a1; // expected-error {{attempt to use a deleted function}} + A<2> a2; // expected-error {{attempt to use a deleted function}} + B<1> b1; // expected-error {{attempt to use a deleted function}} + B<2> b2; // expected-error {{attempt to use a deleted function}} + C<1> c1; // expected-error {{attempt to use a deleted function}} + C<2> c2; // expected-error {{attempt to use a deleted function}} + D<0> d0; + D<1> d1; + D<2> d2; // expected-error {{attempt to use a deleted function}} + E<1> e1; // expected-error {{attempt to use a deleted function}} +} diff --git a/clang/test/CXX/over/over.match/over.match.viable/p3.cpp b/clang/test/CXX/over/over.match/over.match.viable/p3.cpp --- a/clang/test/CXX/over/over.match/over.match.viable/p3.cpp +++ b/clang/test/CXX/over/over.match/over.match.viable/p3.cpp @@ -49,7 +49,6 @@ S(A) requires false; S(double) requires true; ~S() requires false; - // expected-note@-1 2{{because 'false' evaluated to false}} ~S() requires true; operator int() requires true; operator int() requires false; @@ -58,11 +57,7 @@ void bar() { WrapsStatics::foo(A{}); S{1.}.foo(A{}); - // expected-error@-1{{invalid reference to function '~S': constraints not satisfied}} - // Note - this behavior w.r.t. constrained dtors is a consequence of current - // wording, which does not invoke overload resolution when a dtor is called. - // P0848 is set to address this issue. + S s = 1; - // expected-error@-1{{invalid reference to function '~S': constraints not satisfied}} int a = s; }