Skip to content

Commit 4a4e90a

Browse files
committed
[c++20] Compute exception specifications for defaulted comparisons.
This requires us to essentially fully form the body of the defaulted comparison, but from an unevaluated context. Naively this would require generating the function definition twice; instead, we ensure that the function body is implicitly defined before performing the check, and walk the actual body where possible.
1 parent fbf60b7 commit 4a4e90a

File tree

6 files changed

+360
-68
lines changed

6 files changed

+360
-68
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5232,7 +5232,10 @@ class Sema final {
52325232
void CalledDecl(SourceLocation CallLoc, const CXXMethodDecl *Method);
52335233

52345234
/// Integrate an invoked expression into the collected data.
5235-
void CalledExpr(Expr *E);
5235+
void CalledExpr(Expr *E) { CalledStmt(E); }
5236+
5237+
/// Integrate an invoked statement into the collected data.
5238+
void CalledStmt(Stmt *S);
52365239

52375240
/// Overwrite an EPI's exception specification with this
52385241
/// computed exception specification.
@@ -5294,7 +5297,7 @@ class Sema final {
52945297

52955298
/// Evaluate the implicit exception specification for a defaulted
52965299
/// special member function.
5297-
void EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD);
5300+
void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD);
52985301

52995302
/// Check the given noexcept-specifier, convert its expression, and compute
53005303
/// the appropriate ExceptionSpecificationType.

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 106 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc,
217217
Exceptions.push_back(E);
218218
}
219219

220-
void Sema::ImplicitExceptionSpecification::CalledExpr(Expr *E) {
221-
if (!E || ComputedEST == EST_MSAny)
220+
void Sema::ImplicitExceptionSpecification::CalledStmt(Stmt *S) {
221+
if (!S || ComputedEST == EST_MSAny)
222222
return;
223223

224224
// FIXME:
@@ -242,7 +242,7 @@ void Sema::ImplicitExceptionSpecification::CalledExpr(Expr *E) {
242242
// implicit definition. For now, we assume that any non-nothrow expression can
243243
// throw any exception.
244244

245-
if (Self->canThrow(E))
245+
if (Self->canThrow(S))
246246
ComputedEST = EST_None;
247247
}
248248

@@ -6814,20 +6814,50 @@ static bool defaultedSpecialMemberIsConstexpr(
68146814
return true;
68156815
}
68166816

6817+
namespace {
6818+
/// RAII object to register a defaulted function as having its exception
6819+
/// specification computed.
6820+
struct ComputingExceptionSpec {
6821+
Sema &S;
6822+
6823+
ComputingExceptionSpec(Sema &S, FunctionDecl *FD, SourceLocation Loc)
6824+
: S(S) {
6825+
Sema::CodeSynthesisContext Ctx;
6826+
Ctx.Kind = Sema::CodeSynthesisContext::ExceptionSpecEvaluation;
6827+
Ctx.PointOfInstantiation = Loc;
6828+
Ctx.Entity = FD;
6829+
S.pushCodeSynthesisContext(Ctx);
6830+
}
6831+
~ComputingExceptionSpec() {
6832+
S.popCodeSynthesisContext();
6833+
}
6834+
};
6835+
}
6836+
68176837
static Sema::ImplicitExceptionSpecification
68186838
ComputeDefaultedSpecialMemberExceptionSpec(
68196839
Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM,
68206840
Sema::InheritedConstructorInfo *ICI);
68216841

68226842
static Sema::ImplicitExceptionSpecification
6823-
computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, CXXMethodDecl *MD) {
6824-
auto CSM = S.getSpecialMember(MD);
6825-
if (CSM != Sema::CXXInvalid)
6826-
return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, MD, CSM, nullptr);
6843+
ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
6844+
FunctionDecl *FD,
6845+
Sema::DefaultedComparisonKind DCK);
68276846

6828-
auto *CD = cast<CXXConstructorDecl>(MD);
6847+
static Sema::ImplicitExceptionSpecification
6848+
computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) {
6849+
auto DFK = S.getDefaultedFunctionKind(FD);
6850+
if (DFK.isSpecialMember())
6851+
return ComputeDefaultedSpecialMemberExceptionSpec(
6852+
S, Loc, cast<CXXMethodDecl>(FD), DFK.asSpecialMember(), nullptr);
6853+
if (DFK.isComparison())
6854+
return ComputeDefaultedComparisonExceptionSpec(S, Loc, FD,
6855+
DFK.asComparison());
6856+
6857+
auto *CD = cast<CXXConstructorDecl>(FD);
68296858
assert(CD->getInheritedConstructor() &&
6830-
"only special members have implicit exception specs");
6859+
"only defaulted functions and inherited constructors have implicit "
6860+
"exception specs");
68316861
Sema::InheritedConstructorInfo ICI(
68326862
S, Loc, CD->getInheritedConstructor().getShadowDecl());
68336863
return ComputeDefaultedSpecialMemberExceptionSpec(
@@ -6849,25 +6879,17 @@ static FunctionProtoType::ExtProtoInfo getImplicitMethodEPI(Sema &S,
68496879
return EPI;
68506880
}
68516881

6852-
void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD) {
6853-
const FunctionProtoType *FPT = MD->getType()->castAs<FunctionProtoType>();
6882+
void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD) {
6883+
const FunctionProtoType *FPT = FD->getType()->castAs<FunctionProtoType>();
68546884
if (FPT->getExceptionSpecType() != EST_Unevaluated)
68556885
return;
68566886

68576887
// Evaluate the exception specification.
6858-
auto IES = computeImplicitExceptionSpec(*this, Loc, MD);
6888+
auto IES = computeImplicitExceptionSpec(*this, Loc, FD);
68596889
auto ESI = IES.getExceptionSpec();
68606890

68616891
// Update the type of the special member to use it.
6862-
UpdateExceptionSpec(MD, ESI);
6863-
6864-
// A user-provided destructor can be defined outside the class. When that
6865-
// happens, be sure to update the exception specification on both
6866-
// declarations.
6867-
const FunctionProtoType *CanonicalFPT =
6868-
MD->getCanonicalDecl()->getType()->castAs<FunctionProtoType>();
6869-
if (CanonicalFPT->getExceptionSpecType() == EST_Unevaluated)
6870-
UpdateExceptionSpec(MD->getCanonicalDecl(), ESI);
6892+
UpdateExceptionSpec(FD, ESI);
68716893
}
68726894

68736895
void Sema::CheckExplicitlyDefaultedFunction(Scope *S, FunctionDecl *FD) {
@@ -8092,12 +8114,20 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
80928114
// declaration, it is implicitly considered to be constexpr.
80938115
// FIXME: Only applying this to the first declaration seems problematic, as
80948116
// simple reorderings can affect the meaning of the program.
8095-
if (First) {
8096-
if (!FD->isConstexpr() && Info.Constexpr)
8097-
FD->setConstexprKind(CSK_constexpr);
8098-
8099-
// FIXME: Set up an implicit exception specification, or if given an
8100-
// explicit one, check that it matches.
8117+
if (First && !FD->isConstexpr() && Info.Constexpr)
8118+
FD->setConstexprKind(CSK_constexpr);
8119+
8120+
// C++2a [except.spec]p3:
8121+
// If a declaration of a function does not have a noexcept-specifier
8122+
// [and] is defaulted on its first declaration, [...] the exception
8123+
// specification is as specified below
8124+
if (FD->getExceptionSpecType() == EST_None) {
8125+
auto *FPT = FD->getType()->castAs<FunctionProtoType>();
8126+
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
8127+
EPI.ExceptionSpec.Type = EST_Unevaluated;
8128+
EPI.ExceptionSpec.SourceDecl = FD;
8129+
FD->setType(Context.getFunctionType(FPT->getReturnType(),
8130+
FPT->getParamTypes(), EPI));
81018131
}
81028132

81038133
return false;
@@ -8126,17 +8156,11 @@ void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
81268156

81278157
SynthesizedFunctionScope Scope(*this, FD);
81288158

8129-
// The exception specification is needed because we are defining the
8130-
// function.
8131-
// FIXME: Handle this better. Computing the exception specification will
8132-
// eventually need the function body.
8133-
ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
8134-
81358159
// Add a context note for diagnostics produced after this point.
81368160
Scope.addContextNote(UseLoc);
81378161

8138-
// Build and set up the function body.
81398162
{
8163+
// Build and set up the function body.
81408164
CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
81418165
SourceLocation BodyLoc =
81428166
FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
@@ -8150,10 +8174,58 @@ void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
81508174
FD->markUsed(Context);
81518175
}
81528176

8177+
// The exception specification is needed because we are defining the
8178+
// function. Note that this will reuse the body we just built.
8179+
ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
8180+
81538181
if (ASTMutationListener *L = getASTMutationListener())
81548182
L->CompletedImplicitDefinition(FD);
81558183
}
81568184

8185+
static Sema::ImplicitExceptionSpecification
8186+
ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
8187+
FunctionDecl *FD,
8188+
Sema::DefaultedComparisonKind DCK) {
8189+
ComputingExceptionSpec CES(S, FD, Loc);
8190+
Sema::ImplicitExceptionSpecification ExceptSpec(S);
8191+
8192+
if (FD->isInvalidDecl())
8193+
return ExceptSpec;
8194+
8195+
// The common case is that we just defined the comparison function. In that
8196+
// case, just look at whether the body can throw.
8197+
if (FD->hasBody()) {
8198+
ExceptSpec.CalledStmt(FD->getBody());
8199+
} else {
8200+
// Otherwise, build a body so we can check it. This should ideally only
8201+
// happen when we're not actually marking the function referenced. (This is
8202+
// only really important for efficiency: we don't want to build and throw
8203+
// away bodies for comparison functions more than we strictly need to.)
8204+
8205+
// Pretend to synthesize the function body in an unevaluated context.
8206+
// Note that we can't actually just go ahead and define the function here:
8207+
// we are not permitted to mark its callees as referenced.
8208+
Sema::SynthesizedFunctionScope Scope(S, FD);
8209+
EnterExpressionEvaluationContext Context(
8210+
S, Sema::ExpressionEvaluationContext::Unevaluated);
8211+
8212+
CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
8213+
SourceLocation BodyLoc =
8214+
FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
8215+
StmtResult Body =
8216+
DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
8217+
if (!Body.isInvalid())
8218+
ExceptSpec.CalledStmt(Body.get());
8219+
8220+
// FIXME: Can we hold onto this body and just transform it to potentially
8221+
// evaluated when we're asked to define the function rather than rebuilding
8222+
// it? Either that, or we should only build the bits of the body that we
8223+
// need (the expressions, not the statements).
8224+
}
8225+
8226+
return ExceptSpec;
8227+
}
8228+
81578229
void Sema::CheckDelayedMemberExceptionSpecs() {
81588230
decltype(DelayedOverridingExceptionSpecChecks) Overriding;
81598231
decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;
@@ -12336,25 +12408,6 @@ void SpecialMemberExceptionSpecInfo::visitSubobjectCall(
1233612408
ExceptSpec.CalledDecl(getSubobjectLoc(Subobj), MD);
1233712409
}
1233812410

12339-
namespace {
12340-
/// RAII object to register a special member as being currently declared.
12341-
struct ComputingExceptionSpec {
12342-
Sema &S;
12343-
12344-
ComputingExceptionSpec(Sema &S, CXXMethodDecl *MD, SourceLocation Loc)
12345-
: S(S) {
12346-
Sema::CodeSynthesisContext Ctx;
12347-
Ctx.Kind = Sema::CodeSynthesisContext::ExceptionSpecEvaluation;
12348-
Ctx.PointOfInstantiation = Loc;
12349-
Ctx.Entity = MD;
12350-
S.pushCodeSynthesisContext(Ctx);
12351-
}
12352-
~ComputingExceptionSpec() {
12353-
S.popCodeSynthesisContext();
12354-
}
12355-
};
12356-
}
12357-
1235812411
bool Sema::tryResolveExplicitSpecifier(ExplicitSpecifier &ExplicitSpec) {
1235912412
llvm::APSInt Result;
1236012413
ExprResult Converted = CheckConvertedConstantExpression(

clang/lib/Sema/SemaExceptionSpec.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ Sema::ResolveExceptionSpec(SourceLocation Loc, const FunctionProtoType *FPT) {
205205

206206
// Compute or instantiate the exception specification now.
207207
if (SourceFPT->getExceptionSpecType() == EST_Unevaluated)
208-
EvaluateImplicitExceptionSpec(Loc, cast<CXXMethodDecl>(SourceDecl));
208+
EvaluateImplicitExceptionSpec(Loc, SourceDecl);
209209
else
210210
InstantiateExceptionSpec(Loc, SourceDecl);
211211

@@ -1221,6 +1221,17 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
12211221
return mergeCanThrow(CT, canSubStmtsThrow(*this, BTE));
12221222
}
12231223

1224+
case Expr::PseudoObjectExprClass: {
1225+
auto *POE = cast<PseudoObjectExpr>(S);
1226+
CanThrowResult CT = CT_Cannot;
1227+
for (const Expr *E : POE->semantics()) {
1228+
CT = mergeCanThrow(CT, canThrow(E));
1229+
if (CT == CT_Can)
1230+
break;
1231+
}
1232+
return CT;
1233+
}
1234+
12241235
// ObjC message sends are like function calls, but never have exception
12251236
// specs.
12261237
case Expr::ObjCMessageExprClass:
@@ -1327,15 +1338,14 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
13271338
case Expr::ObjCAvailabilityCheckExprClass:
13281339
case Expr::OffsetOfExprClass:
13291340
case Expr::PackExpansionExprClass:
1330-
case Expr::PseudoObjectExprClass:
13311341
case Expr::SubstNonTypeTemplateParmExprClass:
13321342
case Expr::SubstNonTypeTemplateParmPackExprClass:
13331343
case Expr::FunctionParmPackExprClass:
13341344
case Expr::UnaryExprOrTypeTraitExprClass:
13351345
case Expr::UnresolvedLookupExprClass:
13361346
case Expr::UnresolvedMemberExprClass:
13371347
case Expr::TypoExprClass:
1338-
// FIXME: Can any of the above throw? If so, when?
1348+
// FIXME: Many of the above can throw.
13391349
return CT_Cannot;
13401350

13411351
case Expr::AddrLabelExprClass:

clang/test/CXX/class/class.compare/class.compare.default/p3.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ struct A {
2424
friend bool operator>=(const A&, const A&) = default;
2525
};
2626
struct TestA {
27-
friend constexpr bool operator==(const A&, const A&);
28-
friend constexpr bool operator!=(const A&, const A&);
27+
friend constexpr bool operator==(const A&, const A&) noexcept;
28+
friend constexpr bool operator!=(const A&, const A&) noexcept;
2929

30-
friend constexpr std::strong_ordering operator<=>(const A&, const A&);
30+
friend constexpr std::strong_ordering operator<=>(const A&, const A&) noexcept;
3131
friend constexpr bool operator<(const A&, const A&);
3232
friend constexpr bool operator<=(const A&, const A&);
3333
friend constexpr bool operator>(const A&, const A&);
@@ -51,10 +51,10 @@ struct TestReversedA {
5151
friend constexpr bool operator>(const ReversedA&, const ReversedA&);
5252
friend constexpr bool operator<=(const ReversedA&, const ReversedA&);
5353
friend constexpr bool operator<(const ReversedA&, const ReversedA&);
54-
friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&);
54+
friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&) noexcept;
5555

56-
friend constexpr bool operator!=(const ReversedA&, const ReversedA&);
57-
friend constexpr bool operator==(const ReversedA&, const ReversedA&);
56+
friend constexpr bool operator!=(const ReversedA&, const ReversedA&) noexcept;
57+
friend constexpr bool operator==(const ReversedA&, const ReversedA&) noexcept;
5858
};
5959

6060
struct B {
@@ -69,8 +69,8 @@ struct B {
6969
friend bool operator>=(const B&, const B&) = default;
7070
};
7171
struct TestB {
72-
friend constexpr bool operator==(const B&, const B&);
73-
friend constexpr bool operator!=(const B&, const B&);
72+
friend constexpr bool operator==(const B&, const B&) noexcept;
73+
friend constexpr bool operator!=(const B&, const B&) noexcept;
7474

7575
friend constexpr std::strong_ordering operator<=>(const B&, const B&);
7676
friend constexpr bool operator<(const B&, const B&);

clang/test/CXX/class/class.compare/class.compare.default/p4.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ namespace N {
2121

2222
constexpr bool (*test_a_not_found)(const A&, const A&) = &operator==; // expected-error {{undeclared}}
2323

24-
constexpr bool operator==(const A&, const A&);
25-
constexpr bool (*test_a)(const A&, const A&) = &operator==;
24+
constexpr bool operator==(const A&, const A&) noexcept;
25+
constexpr bool (*test_a)(const A&, const A&) noexcept = &operator==;
2626
static_assert((*test_a)(A(), A()));
2727
}
2828

0 commit comments

Comments
 (0)