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/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/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -2320,6 +2320,80 @@ } }; + /// [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. + /// + /// To achieve that, we remove all non-selected destructors from the AST, + /// which is a bit unusual. We can't let those declarations be in AST and rely + /// on LookupSpecialMember to return the correct declaration because a lot of + /// code relies on CXXRecordDecl::getDestructor() which assumes there can only + /// be one destructor for a class. + /// The reason that we actually have to insert those declarations in the first + /// place (instead of keeping them at the side and adding the selected one + /// later) is that this will change the order of declarations in the class + /// which determines the vtable order, so this would break ABI unless we + /// insert the destructor at the original location, at which point it is + /// easier to just remove the unselected ones. + LLVM_ATTRIBUTE_NOINLINE // Noinline e're sensitive about the stack size of `InstantiateClass` + void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Instantiation) { + + if (!Instantiation->hasUserDeclaredDestructor()) + return; + + // FIXME: Is this the correct location? + SourceLocation Loc = Instantiation->getLocation(); + OverloadCandidateSet OCS(Loc, OverloadCandidateSet::CSK_Normal); + + SmallVector ProspectiveDestructors; + for (auto *Decl : Instantiation->decls()) { + if (auto *DD = dyn_cast(Decl)) { + if (DD->isInvalidDecl()) + continue; + ProspectiveDestructors.push_back(DD); + S.AddOverloadCandidate(DD, DeclAccessPair::make(DD, DD->getAccess()), + {}, OCS); + } + } + assert( + !OCS.empty() && + "No candidates found even though hasUserDeclaredDestructor is true."); + + OverloadCandidateSet::iterator Best; + unsigned Msg = 0; + OverloadCandidateDisplayKind DisplayKind; + + switch (OCS.BestViableFunction(S, Loc, Best)) { + case OR_Success: + case OR_Deleted: + for (auto *DD : ProspectiveDestructors) { + if (DD != Best->Function) { + Instantiation->removeDecl(DD); + // FIXME: We should maybe `delete DD` here, but it segfaults. + } + } + 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) << Instantiation; + OCS.NoteCandidates(PartialDiagnosticAt(Loc, Diag), S, DisplayKind, {}); + Instantiation->setInvalidDecl(); + } + } + } // namespace bool Sema::SubstTypeConstraint( @@ -2789,6 +2863,9 @@ } } + if (getLangOpts().CPlusPlus20) + ComputeSelectedDestructor(*this, Instantiation); + // Finish checking fields. ActOnFields(nullptr, Instantiation->getLocation(), Instantiation, Fields, SourceLocation(), SourceLocation(), ParsedAttributesView()); 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; }