Skip to content

Commit 21eb1af

Browse files
committed
[Concepts] Implement overload resolution for destructors (P0848)
This patch implements a necessary part of P0848, the overload resolution for destructors. It is now possible to overload destructors based on constraints, and the eligible destructor will be selected at the end of the class. The approach this patch takes is to perform the overload resolution in Sema::ActOnFields and to mark the selected destructor using a new property in FunctionDeclBitfields. CXXRecordDecl::getDestructor is then modified to use this property to return the correct destructor. This closes #45614. Reviewed By: #clang-language-wg, erichkeane Differential Revision: https://reviews.llvm.org/D126194
1 parent b911cbd commit 21eb1af

File tree

17 files changed

+386
-57
lines changed

17 files changed

+386
-57
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,10 @@ C++20 Feature Support
446446
that can be used for such compatibility. The demangler now demangles
447447
symbols with named module attachment.
448448

449+
- As per "Conditionally Trivial Special Member Functions" (P0848), it is
450+
now possible to overload destructors using concepts. Note that the rest
451+
of the paper about other special member functions is not yet implemented.
452+
449453
C++2b Feature Support
450454
^^^^^^^^^^^^^^^^^^^^^
451455

clang/include/clang/AST/Decl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,13 @@ class FunctionDecl : public DeclaratorDecl,
22512251
DeclAsWritten->getCanonicalDecl()->isDefaulted());
22522252
}
22532253

2254+
bool isIneligibleOrNotSelected() const {
2255+
return FunctionDeclBits.IsIneligibleOrNotSelected;
2256+
}
2257+
void setIneligibleOrNotSelected(bool II) {
2258+
FunctionDeclBits.IsIneligibleOrNotSelected = II;
2259+
}
2260+
22542261
/// Whether falling off this function implicitly returns null/zero.
22552262
/// If a more specific implicit return value is required, front-ends
22562263
/// should synthesize the appropriate return statements.

clang/include/clang/AST/DeclBase.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,12 @@ class DeclContext {
15961596
uint64_t IsDefaulted : 1;
15971597
uint64_t IsExplicitlyDefaulted : 1;
15981598
uint64_t HasDefaultedFunctionInfo : 1;
1599+
1600+
/// For member functions of complete types, whether this is an ineligible
1601+
/// special member function or an unselected destructor. See
1602+
/// [class.mem.special].
1603+
uint64_t IsIneligibleOrNotSelected : 1;
1604+
15991605
uint64_t HasImplicitReturnZero : 1;
16001606
uint64_t IsLateTemplateParsed : 1;
16011607

@@ -1631,7 +1637,7 @@ class DeclContext {
16311637
};
16321638

16331639
/// Number of non-inherited bits in FunctionDeclBitfields.
1634-
enum { NumFunctionDeclBits = 27 };
1640+
enum { NumFunctionDeclBits = 28 };
16351641

16361642
/// Stores the bits used by CXXConstructorDecl. If modified
16371643
/// NumCXXConstructorDeclBits and the accessor
@@ -1643,12 +1649,12 @@ class DeclContext {
16431649
/// For the bits in FunctionDeclBitfields.
16441650
uint64_t : NumFunctionDeclBits;
16451651

1646-
/// 24 bits to fit in the remaining available space.
1652+
/// 23 bits to fit in the remaining available space.
16471653
/// Note that this makes CXXConstructorDeclBitfields take
16481654
/// exactly 64 bits and thus the width of NumCtorInitializers
16491655
/// will need to be shrunk if some bit is added to NumDeclContextBitfields,
16501656
/// NumFunctionDeclBitfields or CXXConstructorDeclBitfields.
1651-
uint64_t NumCtorInitializers : 21;
1657+
uint64_t NumCtorInitializers : 20;
16521658
uint64_t IsInheritingConstructor : 1;
16531659

16541660
/// Whether this constructor has a trail-allocated explicit specifier.

clang/include/clang/AST/DeclCXX.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,19 @@ class CXXRecordDecl : public RecordDecl {
14221422
return isLiteral() && data().StructuralIfLiteral;
14231423
}
14241424

1425+
/// Notify the class that this destructor is now selected.
1426+
///
1427+
/// Important properties of the class depend on destructor properties. Since
1428+
/// C++20, it is possible to have multiple destructor declarations in a class
1429+
/// out of which one will be selected at the end.
1430+
/// This is called separately from addedMember because it has to be deferred
1431+
/// to the completion of the class.
1432+
void addedSelectedDestructor(CXXDestructorDecl *DD);
1433+
1434+
/// Notify the class that an eligible SMF has been added.
1435+
/// This updates triviality and destructor based properties of the class accordingly.
1436+
void addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, unsigned SMKind);
1437+
14251438
/// If this record is an instantiation of a member class,
14261439
/// retrieves the member class from which it was instantiated.
14271440
///

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4716,6 +4716,10 @@ def err_bound_member_function : Error<
47164716
"reference to non-static member function must be called"
47174717
"%select{|; did you mean to call it with no arguments?}0">;
47184718
def note_possible_target_of_call : Note<"possible target for call">;
4719+
def err_no_viable_destructor : Error<
4720+
"no viable destructor found for class %0">;
4721+
def err_ambiguous_destructor : Error<
4722+
"destructor of class %0 is ambiguous">;
47194723

47204724
def err_ovl_no_viable_object_call : Error<
47214725
"no matching function for call to object of type %0">;

clang/lib/AST/Decl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2955,6 +2955,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
29552955
FunctionDeclBits.IsDefaulted = false;
29562956
FunctionDeclBits.IsExplicitlyDefaulted = false;
29572957
FunctionDeclBits.HasDefaultedFunctionInfo = false;
2958+
FunctionDeclBits.IsIneligibleOrNotSelected = false;
29582959
FunctionDeclBits.HasImplicitReturnZero = false;
29592960
FunctionDeclBits.IsLateTemplateParsed = false;
29602961
FunctionDeclBits.ConstexprKind = static_cast<uint64_t>(ConstexprKind);

clang/lib/AST/DeclCXX.cpp

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -825,29 +825,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
825825
data().HasInheritedDefaultConstructor = true;
826826
}
827827

828-
// Handle destructors.
829-
if (const auto *DD = dyn_cast<CXXDestructorDecl>(D)) {
830-
SMKind |= SMF_Destructor;
831-
832-
if (DD->isUserProvided())
833-
data().HasIrrelevantDestructor = false;
834-
// If the destructor is explicitly defaulted and not trivial or not public
835-
// or if the destructor is deleted, we clear HasIrrelevantDestructor in
836-
// finishedDefaultedOrDeletedMember.
837-
838-
// C++11 [class.dtor]p5:
839-
// A destructor is trivial if [...] the destructor is not virtual.
840-
if (DD->isVirtual()) {
841-
data().HasTrivialSpecialMembers &= ~SMF_Destructor;
842-
data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor;
843-
}
844-
845-
if (DD->isNoReturn())
846-
data().IsAnyDestructorNoReturn = true;
847-
}
848-
849828
// Handle member functions.
850829
if (const auto *Method = dyn_cast<CXXMethodDecl>(D)) {
830+
if (const auto *DD = dyn_cast<CXXDestructorDecl>(D))
831+
SMKind |= SMF_Destructor;
832+
851833
if (Method->isCopyAssignmentOperator()) {
852834
SMKind |= SMF_CopyAssignment;
853835

@@ -893,31 +875,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
893875
data().HasTrivialSpecialMembersForCall &=
894876
data().DeclaredSpecialMembers | ~SMKind;
895877

896-
if (!Method->isImplicit() && !Method->isUserProvided()) {
897-
// This method is user-declared but not user-provided. We can't work out
898-
// whether it's trivial yet (not until we get to the end of the class).
899-
// We'll handle this method in finishedDefaultedOrDeletedMember.
900-
} else if (Method->isTrivial()) {
901-
data().HasTrivialSpecialMembers |= SMKind;
902-
data().HasTrivialSpecialMembersForCall |= SMKind;
903-
} else if (Method->isTrivialForCall()) {
904-
data().HasTrivialSpecialMembersForCall |= SMKind;
905-
data().DeclaredNonTrivialSpecialMembers |= SMKind;
906-
} else {
907-
data().DeclaredNonTrivialSpecialMembers |= SMKind;
908-
// If this is a user-provided function, do not set
909-
// DeclaredNonTrivialSpecialMembersForCall here since we don't know
910-
// yet whether the method would be considered non-trivial for the
911-
// purpose of calls (attribute "trivial_abi" can be dropped from the
912-
// class later, which can change the special method's triviality).
913-
if (!Method->isUserProvided())
914-
data().DeclaredNonTrivialSpecialMembersForCall |= SMKind;
915-
}
916-
917878
// Note when we have declared a declared special member, and suppress the
918879
// implicit declaration of this special member.
919880
data().DeclaredSpecialMembers |= SMKind;
920-
921881
if (!Method->isImplicit()) {
922882
data().UserDeclaredSpecialMembers |= SMKind;
923883

@@ -934,6 +894,12 @@ void CXXRecordDecl::addedMember(Decl *D) {
934894
// This is an extension in C++03.
935895
data().PlainOldData = false;
936896
}
897+
// We delay updating destructor relevant properties until
898+
// addedSelectedDestructor.
899+
// FIXME: Defer this for the other special member functions as well.
900+
if (!Method->isIneligibleOrNotSelected()) {
901+
addedEligibleSpecialMemberFunction(Method, SMKind);
902+
}
937903
}
938904

939905
return;
@@ -1393,6 +1359,54 @@ void CXXRecordDecl::addedMember(Decl *D) {
13931359
}
13941360
}
13951361

1362+
void CXXRecordDecl::addedSelectedDestructor(CXXDestructorDecl *DD) {
1363+
DD->setIneligibleOrNotSelected(false);
1364+
addedEligibleSpecialMemberFunction(DD, SMF_Destructor);
1365+
}
1366+
1367+
void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
1368+
unsigned SMKind) {
1369+
if (const auto *DD = dyn_cast<CXXDestructorDecl>(MD)) {
1370+
if (DD->isUserProvided())
1371+
data().HasIrrelevantDestructor = false;
1372+
// If the destructor is explicitly defaulted and not trivial or not public
1373+
// or if the destructor is deleted, we clear HasIrrelevantDestructor in
1374+
// finishedDefaultedOrDeletedMember.
1375+
1376+
// C++11 [class.dtor]p5:
1377+
// A destructor is trivial if [...] the destructor is not virtual.
1378+
if (DD->isVirtual()) {
1379+
data().HasTrivialSpecialMembers &= ~SMF_Destructor;
1380+
data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor;
1381+
}
1382+
1383+
if (DD->isNoReturn())
1384+
data().IsAnyDestructorNoReturn = true;
1385+
}
1386+
1387+
if (!MD->isImplicit() && !MD->isUserProvided()) {
1388+
// This method is user-declared but not user-provided. We can't work
1389+
// out whether it's trivial yet (not until we get to the end of the
1390+
// class). We'll handle this method in
1391+
// finishedDefaultedOrDeletedMember.
1392+
} else if (MD->isTrivial()) {
1393+
data().HasTrivialSpecialMembers |= SMKind;
1394+
data().HasTrivialSpecialMembersForCall |= SMKind;
1395+
} else if (MD->isTrivialForCall()) {
1396+
data().HasTrivialSpecialMembersForCall |= SMKind;
1397+
data().DeclaredNonTrivialSpecialMembers |= SMKind;
1398+
} else {
1399+
data().DeclaredNonTrivialSpecialMembers |= SMKind;
1400+
// If this is a user-provided function, do not set
1401+
// DeclaredNonTrivialSpecialMembersForCall here since we don't know
1402+
// yet whether the method would be considered non-trivial for the
1403+
// purpose of calls (attribute "trivial_abi" can be dropped from the
1404+
// class later, which can change the special method's triviality).
1405+
if (!MD->isUserProvided())
1406+
data().DeclaredNonTrivialSpecialMembersForCall |= SMKind;
1407+
}
1408+
}
1409+
13961410
void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
13971411
assert(!D->isImplicit() && !D->isUserProvided());
13981412

@@ -1895,7 +1909,14 @@ CXXDestructorDecl *CXXRecordDecl::getDestructor() const {
18951909

18961910
DeclContext::lookup_result R = lookup(Name);
18971911

1898-
return R.empty() ? nullptr : dyn_cast<CXXDestructorDecl>(R.front());
1912+
// If a destructor was marked as not selected, we skip it. We don't always
1913+
// have a selected destructor: dependent types, unnamed structs.
1914+
for (auto *Decl : R) {
1915+
auto* DD = dyn_cast<CXXDestructorDecl>(Decl);
1916+
if (DD && !DD->isIneligibleOrNotSelected())
1917+
return DD;
1918+
}
1919+
return nullptr;
18991920
}
19001921

19011922
static bool isDeclContextInNamespace(const DeclContext *DC) {

clang/lib/AST/TextNodeDumper.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,6 +1681,9 @@ void TextNodeDumper::VisitFunctionDecl(const FunctionDecl *D) {
16811681
if (D->isTrivial())
16821682
OS << " trivial";
16831683

1684+
if (D->isIneligibleOrNotSelected())
1685+
OS << (isa<CXXDestructorDecl>(D) ? " not_selected" : " ineligible");
1686+
16841687
if (const auto *FPT = D->getType()->getAs<FunctionProtoType>()) {
16851688
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
16861689
switch (EPI.ExceptionSpec.Type) {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8852,6 +8852,10 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
88528852
SemaRef.getCurFPFeatures().isFPConstrained(), isInline,
88538853
/*isImplicitlyDeclared=*/false, ConstexprKind,
88548854
TrailingRequiresClause);
8855+
// User defined destructors start as not selected if the class definition is still
8856+
// not done.
8857+
if (Record->isBeingDefined())
8858+
NewDD->setIneligibleOrNotSelected(true);
88558859

88568860
// If the destructor needs an implicit exception specification, set it
88578861
// now. FIXME: It'd be nice to be able to create the right type to start
@@ -17711,6 +17715,75 @@ void Sema::ActOnLastBitfield(SourceLocation DeclLoc,
1771117715
AllIvarDecls.push_back(Ivar);
1771217716
}
1771317717

17718+
namespace {
17719+
/// [class.dtor]p4:
17720+
/// At the end of the definition of a class, overload resolution is
17721+
/// performed among the prospective destructors declared in that class with
17722+
/// an empty argument list to select the destructor for the class, also
17723+
/// known as the selected destructor.
17724+
///
17725+
/// We do the overload resolution here, then mark the selected constructor in the AST.
17726+
/// Later CXXRecordDecl::getDestructor() will return the selected constructor.
17727+
void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
17728+
if (!Record->hasUserDeclaredDestructor()) {
17729+
return;
17730+
}
17731+
17732+
SourceLocation Loc = Record->getLocation();
17733+
OverloadCandidateSet OCS(Loc, OverloadCandidateSet::CSK_Normal);
17734+
17735+
for (auto *Decl : Record->decls()) {
17736+
if (auto *DD = dyn_cast<CXXDestructorDecl>(Decl)) {
17737+
if (DD->isInvalidDecl())
17738+
continue;
17739+
S.AddOverloadCandidate(DD, DeclAccessPair::make(DD, DD->getAccess()), {},
17740+
OCS);
17741+
assert(DD->isIneligibleOrNotSelected() && "Selecting a destructor but a destructor was already selected.");
17742+
}
17743+
}
17744+
17745+
if (OCS.empty()) {
17746+
return;
17747+
}
17748+
OverloadCandidateSet::iterator Best;
17749+
unsigned Msg = 0;
17750+
OverloadCandidateDisplayKind DisplayKind;
17751+
17752+
switch (OCS.BestViableFunction(S, Loc, Best)) {
17753+
case OR_Success:
17754+
case OR_Deleted:
17755+
Record->addedSelectedDestructor(dyn_cast<CXXDestructorDecl>(Best->Function));
17756+
break;
17757+
17758+
case OR_Ambiguous:
17759+
Msg = diag::err_ambiguous_destructor;
17760+
DisplayKind = OCD_AmbiguousCandidates;
17761+
break;
17762+
17763+
case OR_No_Viable_Function:
17764+
Msg = diag::err_no_viable_destructor;
17765+
DisplayKind = OCD_AllCandidates;
17766+
break;
17767+
}
17768+
17769+
if (Msg) {
17770+
// OpenCL have got their own thing going with destructors. It's slightly broken,
17771+
// but we allow it.
17772+
if (!S.LangOpts.OpenCL) {
17773+
PartialDiagnostic Diag = S.PDiag(Msg) << Record;
17774+
OCS.NoteCandidates(PartialDiagnosticAt(Loc, Diag), S, DisplayKind, {});
17775+
Record->setInvalidDecl();
17776+
}
17777+
// It's a bit hacky: At this point we've raised an error but we want the
17778+
// rest of the compiler to continue somehow working. However almost
17779+
// everything we'll try to do with the class will depend on there being a
17780+
// destructor. So let's pretend the first one is selected and hope for the
17781+
// best.
17782+
Record->addedSelectedDestructor(dyn_cast<CXXDestructorDecl>(OCS.begin()->Function));
17783+
}
17784+
}
17785+
} // namespace
17786+
1771417787
void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1771517788
ArrayRef<Decl *> Fields, SourceLocation LBrac,
1771617789
SourceLocation RBrac,
@@ -17737,6 +17810,9 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1773717810
RecordDecl *Record = dyn_cast<RecordDecl>(EnclosingDecl);
1773817811
CXXRecordDecl *CXXRecord = dyn_cast<CXXRecordDecl>(EnclosingDecl);
1773917812

17813+
if (CXXRecord && !CXXRecord->isDependentType())
17814+
ComputeSelectedDestructor(*this, CXXRecord);
17815+
1774017816
// Start counting up the number of named members; make sure to include
1774117817
// members of anonymous structs and unions in the total.
1774217818
unsigned NumNamedMembers = 0;

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6693,7 +6693,7 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
66936693
return false;
66946694

66956695
for (const CXXMethodDecl *MD : D->methods()) {
6696-
if (MD->isDeleted())
6696+
if (MD->isDeleted() || MD->isIneligibleOrNotSelected())
66976697
continue;
66986698

66996699
auto *CD = dyn_cast<CXXConstructorDecl>(MD);

clang/lib/Sema/SemaTemplateInstantiate.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3250,6 +3250,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
32503250
if (FunctionDecl *Pattern =
32513251
Function->getInstantiatedFromMemberFunction()) {
32523252

3253+
if (Function->isIneligibleOrNotSelected())
3254+
continue;
3255+
32533256
if (Function->getTrailingRequiresClause()) {
32543257
ConstraintSatisfaction Satisfaction;
32553258
if (CheckFunctionConstraints(Function, Satisfaction) ||

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2478,6 +2478,7 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
24782478
SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
24792479
Destructor->UsesFPIntrin(), Destructor->isInlineSpecified(), false,
24802480
Destructor->getConstexprKind(), TrailingRequiresClause);
2481+
Method->setIneligibleOrNotSelected(true);
24812482
Method->setRangeEnd(Destructor->getEndLoc());
24822483
Method->setDeclName(SemaRef.Context.DeclarationNames.getCXXDestructorName(
24832484
SemaRef.Context.getCanonicalType(

clang/test/AST/ast-dump-decl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ namespace testClassTemplateDecl {
303303
// CHECK-NEXT: | | |-MoveConstructor
304304
// CHECK-NEXT: | | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
305305
// CHECK-NEXT: | | |-MoveAssignment
306-
// CHECK-NEXT: | | `-Destructor non_trivial user_declared
306+
// CHECK-NEXT: | | `-Destructor
307307
// CHECK-NEXT: | |-CXXRecordDecl 0x{{.+}} <col:24, col:30> col:30 implicit referenced class TestClassTemplate
308308
// CHECK-NEXT: | |-AccessSpecDecl 0x{{.+}} <line:[[@LINE-50]]:3, col:9> col:3 public
309309
// CHECK-NEXT: | |-CXXConstructorDecl 0x{{.+}} <line:[[@LINE-50]]:5, col:23> col:5 TestClassTemplate<T> 'void ()'

0 commit comments

Comments
 (0)