void CalledDecl(SourceLocation CallLoc, const CXXMethodDecl *Method);
/// Integrate an invoked expression into the collected data.
- void CalledExpr(Expr *E);
+ void CalledExpr(Expr *E) { CalledStmt(E); }
+
+ /// Integrate an invoked statement into the collected data.
+ void CalledStmt(Stmt *S);
/// Overwrite an EPI's exception specification with this
/// computed exception specification.
/// Evaluate the implicit exception specification for a defaulted
/// special member function.
- void EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD);
+ void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD);
/// Check the given noexcept-specifier, convert its expression, and compute
/// the appropriate ExceptionSpecificationType.
Exceptions.push_back(E);
}
-void Sema::ImplicitExceptionSpecification::CalledExpr(Expr *E) {
- if (!E || ComputedEST == EST_MSAny)
+void Sema::ImplicitExceptionSpecification::CalledStmt(Stmt *S) {
+ if (!S || ComputedEST == EST_MSAny)
return;
// FIXME:
// implicit definition. For now, we assume that any non-nothrow expression can
// throw any exception.
- if (Self->canThrow(E))
+ if (Self->canThrow(S))
ComputedEST = EST_None;
}
return true;
}
+namespace {
+/// RAII object to register a defaulted function as having its exception
+/// specification computed.
+struct ComputingExceptionSpec {
+ Sema &S;
+
+ ComputingExceptionSpec(Sema &S, FunctionDecl *FD, SourceLocation Loc)
+ : S(S) {
+ Sema::CodeSynthesisContext Ctx;
+ Ctx.Kind = Sema::CodeSynthesisContext::ExceptionSpecEvaluation;
+ Ctx.PointOfInstantiation = Loc;
+ Ctx.Entity = FD;
+ S.pushCodeSynthesisContext(Ctx);
+ }
+ ~ComputingExceptionSpec() {
+ S.popCodeSynthesisContext();
+ }
+};
+}
+
static Sema::ImplicitExceptionSpecification
ComputeDefaultedSpecialMemberExceptionSpec(
Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM,
Sema::InheritedConstructorInfo *ICI);
static Sema::ImplicitExceptionSpecification
-computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, CXXMethodDecl *MD) {
- auto CSM = S.getSpecialMember(MD);
- if (CSM != Sema::CXXInvalid)
- return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, MD, CSM, nullptr);
+ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
+ FunctionDecl *FD,
+ Sema::DefaultedComparisonKind DCK);
- auto *CD = cast<CXXConstructorDecl>(MD);
+static Sema::ImplicitExceptionSpecification
+computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) {
+ auto DFK = S.getDefaultedFunctionKind(FD);
+ if (DFK.isSpecialMember())
+ return ComputeDefaultedSpecialMemberExceptionSpec(
+ S, Loc, cast<CXXMethodDecl>(FD), DFK.asSpecialMember(), nullptr);
+ if (DFK.isComparison())
+ return ComputeDefaultedComparisonExceptionSpec(S, Loc, FD,
+ DFK.asComparison());
+
+ auto *CD = cast<CXXConstructorDecl>(FD);
assert(CD->getInheritedConstructor() &&
- "only special members have implicit exception specs");
+ "only defaulted functions and inherited constructors have implicit "
+ "exception specs");
Sema::InheritedConstructorInfo ICI(
S, Loc, CD->getInheritedConstructor().getShadowDecl());
return ComputeDefaultedSpecialMemberExceptionSpec(
return EPI;
}
-void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD) {
- const FunctionProtoType *FPT = MD->getType()->castAs<FunctionProtoType>();
+void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD) {
+ const FunctionProtoType *FPT = FD->getType()->castAs<FunctionProtoType>();
if (FPT->getExceptionSpecType() != EST_Unevaluated)
return;
// Evaluate the exception specification.
- auto IES = computeImplicitExceptionSpec(*this, Loc, MD);
+ auto IES = computeImplicitExceptionSpec(*this, Loc, FD);
auto ESI = IES.getExceptionSpec();
// Update the type of the special member to use it.
- UpdateExceptionSpec(MD, ESI);
-
- // A user-provided destructor can be defined outside the class. When that
- // happens, be sure to update the exception specification on both
- // declarations.
- const FunctionProtoType *CanonicalFPT =
- MD->getCanonicalDecl()->getType()->castAs<FunctionProtoType>();
- if (CanonicalFPT->getExceptionSpecType() == EST_Unevaluated)
- UpdateExceptionSpec(MD->getCanonicalDecl(), ESI);
+ UpdateExceptionSpec(FD, ESI);
}
void Sema::CheckExplicitlyDefaultedFunction(Scope *S, FunctionDecl *FD) {
// declaration, it is implicitly considered to be constexpr.
// FIXME: Only applying this to the first declaration seems problematic, as
// simple reorderings can affect the meaning of the program.
- if (First) {
- if (!FD->isConstexpr() && Info.Constexpr)
- FD->setConstexprKind(CSK_constexpr);
-
- // FIXME: Set up an implicit exception specification, or if given an
- // explicit one, check that it matches.
+ if (First && !FD->isConstexpr() && Info.Constexpr)
+ FD->setConstexprKind(CSK_constexpr);
+
+ // C++2a [except.spec]p3:
+ // If a declaration of a function does not have a noexcept-specifier
+ // [and] is defaulted on its first declaration, [...] the exception
+ // specification is as specified below
+ if (FD->getExceptionSpecType() == EST_None) {
+ auto *FPT = FD->getType()->castAs<FunctionProtoType>();
+ FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+ EPI.ExceptionSpec.Type = EST_Unevaluated;
+ EPI.ExceptionSpec.SourceDecl = FD;
+ FD->setType(Context.getFunctionType(FPT->getReturnType(),
+ FPT->getParamTypes(), EPI));
}
return false;
SynthesizedFunctionScope Scope(*this, FD);
- // The exception specification is needed because we are defining the
- // function.
- // FIXME: Handle this better. Computing the exception specification will
- // eventually need the function body.
- ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
-
// Add a context note for diagnostics produced after this point.
Scope.addContextNote(UseLoc);
- // Build and set up the function body.
{
+ // Build and set up the function body.
CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
SourceLocation BodyLoc =
FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
FD->markUsed(Context);
}
+ // The exception specification is needed because we are defining the
+ // function. Note that this will reuse the body we just built.
+ ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
+
if (ASTMutationListener *L = getASTMutationListener())
L->CompletedImplicitDefinition(FD);
}
+static Sema::ImplicitExceptionSpecification
+ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
+ FunctionDecl *FD,
+ Sema::DefaultedComparisonKind DCK) {
+ ComputingExceptionSpec CES(S, FD, Loc);
+ Sema::ImplicitExceptionSpecification ExceptSpec(S);
+
+ if (FD->isInvalidDecl())
+ return ExceptSpec;
+
+ // The common case is that we just defined the comparison function. In that
+ // case, just look at whether the body can throw.
+ if (FD->hasBody()) {
+ ExceptSpec.CalledStmt(FD->getBody());
+ } else {
+ // Otherwise, build a body so we can check it. This should ideally only
+ // happen when we're not actually marking the function referenced. (This is
+ // only really important for efficiency: we don't want to build and throw
+ // away bodies for comparison functions more than we strictly need to.)
+
+ // Pretend to synthesize the function body in an unevaluated context.
+ // Note that we can't actually just go ahead and define the function here:
+ // we are not permitted to mark its callees as referenced.
+ Sema::SynthesizedFunctionScope Scope(S, FD);
+ EnterExpressionEvaluationContext Context(
+ S, Sema::ExpressionEvaluationContext::Unevaluated);
+
+ CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
+ SourceLocation BodyLoc =
+ FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
+ StmtResult Body =
+ DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
+ if (!Body.isInvalid())
+ ExceptSpec.CalledStmt(Body.get());
+
+ // FIXME: Can we hold onto this body and just transform it to potentially
+ // evaluated when we're asked to define the function rather than rebuilding
+ // it? Either that, or we should only build the bits of the body that we
+ // need (the expressions, not the statements).
+ }
+
+ return ExceptSpec;
+}
+
void Sema::CheckDelayedMemberExceptionSpecs() {
decltype(DelayedOverridingExceptionSpecChecks) Overriding;
decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;
ExceptSpec.CalledDecl(getSubobjectLoc(Subobj), MD);
}
-namespace {
-/// RAII object to register a special member as being currently declared.
-struct ComputingExceptionSpec {
- Sema &S;
-
- ComputingExceptionSpec(Sema &S, CXXMethodDecl *MD, SourceLocation Loc)
- : S(S) {
- Sema::CodeSynthesisContext Ctx;
- Ctx.Kind = Sema::CodeSynthesisContext::ExceptionSpecEvaluation;
- Ctx.PointOfInstantiation = Loc;
- Ctx.Entity = MD;
- S.pushCodeSynthesisContext(Ctx);
- }
- ~ComputingExceptionSpec() {
- S.popCodeSynthesisContext();
- }
-};
-}
-
bool Sema::tryResolveExplicitSpecifier(ExplicitSpecifier &ExplicitSpec) {
llvm::APSInt Result;
ExprResult Converted = CheckConvertedConstantExpression(
// Compute or instantiate the exception specification now.
if (SourceFPT->getExceptionSpecType() == EST_Unevaluated)
- EvaluateImplicitExceptionSpec(Loc, cast<CXXMethodDecl>(SourceDecl));
+ EvaluateImplicitExceptionSpec(Loc, SourceDecl);
else
InstantiateExceptionSpec(Loc, SourceDecl);
return mergeCanThrow(CT, canSubStmtsThrow(*this, BTE));
}
+ case Expr::PseudoObjectExprClass: {
+ auto *POE = cast<PseudoObjectExpr>(S);
+ CanThrowResult CT = CT_Cannot;
+ for (const Expr *E : POE->semantics()) {
+ CT = mergeCanThrow(CT, canThrow(E));
+ if (CT == CT_Can)
+ break;
+ }
+ return CT;
+ }
+
// ObjC message sends are like function calls, but never have exception
// specs.
case Expr::ObjCMessageExprClass:
case Expr::ObjCAvailabilityCheckExprClass:
case Expr::OffsetOfExprClass:
case Expr::PackExpansionExprClass:
- case Expr::PseudoObjectExprClass:
case Expr::SubstNonTypeTemplateParmExprClass:
case Expr::SubstNonTypeTemplateParmPackExprClass:
case Expr::FunctionParmPackExprClass:
case Expr::UnresolvedLookupExprClass:
case Expr::UnresolvedMemberExprClass:
case Expr::TypoExprClass:
- // FIXME: Can any of the above throw? If so, when?
+ // FIXME: Many of the above can throw.
return CT_Cannot;
case Expr::AddrLabelExprClass:
friend bool operator>=(const A&, const A&) = default;
};
struct TestA {
- friend constexpr bool operator==(const A&, const A&);
- friend constexpr bool operator!=(const A&, const A&);
+ friend constexpr bool operator==(const A&, const A&) noexcept;
+ friend constexpr bool operator!=(const A&, const A&) noexcept;
- friend constexpr std::strong_ordering operator<=>(const A&, const A&);
+ friend constexpr std::strong_ordering operator<=>(const A&, const A&) noexcept;
friend constexpr bool operator<(const A&, const A&);
friend constexpr bool operator<=(const A&, const A&);
friend constexpr bool operator>(const A&, const A&);
friend constexpr bool operator>(const ReversedA&, const ReversedA&);
friend constexpr bool operator<=(const ReversedA&, const ReversedA&);
friend constexpr bool operator<(const ReversedA&, const ReversedA&);
- friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&);
+ friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&) noexcept;
- friend constexpr bool operator!=(const ReversedA&, const ReversedA&);
- friend constexpr bool operator==(const ReversedA&, const ReversedA&);
+ friend constexpr bool operator!=(const ReversedA&, const ReversedA&) noexcept;
+ friend constexpr bool operator==(const ReversedA&, const ReversedA&) noexcept;
};
struct B {
friend bool operator>=(const B&, const B&) = default;
};
struct TestB {
- friend constexpr bool operator==(const B&, const B&);
- friend constexpr bool operator!=(const B&, const B&);
+ friend constexpr bool operator==(const B&, const B&) noexcept;
+ friend constexpr bool operator!=(const B&, const B&) noexcept;
friend constexpr std::strong_ordering operator<=>(const B&, const B&);
friend constexpr bool operator<(const B&, const B&);
constexpr bool (*test_a_not_found)(const A&, const A&) = &operator==; // expected-error {{undeclared}}
- constexpr bool operator==(const A&, const A&);
- constexpr bool (*test_a)(const A&, const A&) = &operator==;
+ constexpr bool operator==(const A&, const A&) noexcept;
+ constexpr bool (*test_a)(const A&, const A&) noexcept = &operator==;
static_assert((*test_a)(A(), A()));
}
--- /dev/null
+// RUN: %clang_cc1 -std=c++2a -verify %s
+// RUN: %clang_cc1 -std=c++2a -verify %s -DDEFINE_FIRST
+
+// As modified by P2002R0:
+// The exception specification for a comparison operator function (12.6.2)
+// without a noexcept-specifier that is defaulted on its first declaration is
+// potentially-throwing if and only if any expression in the implicit
+// definition is potentially-throwing.
+
+#define CAT2(a, b) a ## b
+#define CAT(a, b) CAT2(a, b)
+
+#ifdef DEFINE_FIRST
+#define DEF(x) auto CAT(a, __LINE__) = x
+#else
+#define DEF(x)
+#endif
+
+namespace std {
+ struct strong_ordering {
+ int n;
+ static const strong_ordering equal, less, greater;
+ };
+ constexpr strong_ordering strong_ordering::equal{0},
+ strong_ordering::less{-1}, strong_ordering::greater{1};
+ bool operator!=(std::strong_ordering o, int n) noexcept;
+}
+
+namespace Eq {
+ struct A {
+ bool operator==(const A&) const = default;
+ };
+ DEF(A() == A());
+ static_assert(noexcept(A() == A()));
+
+ struct B {
+ bool operator==(const B&) const;
+ };
+ struct C {
+ B b;
+ bool operator==(const C&) const = default;
+ };
+ DEF(C() == C());
+ static_assert(!noexcept(C() == C()));
+
+ // Ensure we do not trigger odr-use from exception specification computation.
+ template<typename T> struct D {
+ bool operator==(const D &) const {
+ typename T::error error; // expected-error {{no type}}
+ }
+ };
+ struct E {
+ D<E> d;
+ bool operator==(const E&) const = default;
+ };
+ static_assert(!noexcept(E() == E()));
+
+ // (but we do when defining the function).
+ struct F {
+ D<F> d;
+ bool operator==(const F&) const = default; // expected-note {{in instantiation}}
+ };
+ bool equal = F() == F();
+ static_assert(!noexcept(F() == F()));
+}
+
+namespace Spaceship {
+ struct X {
+ friend std::strong_ordering operator<=>(X, X);
+ };
+ struct Y : X {
+ friend std::strong_ordering operator<=>(Y, Y) = default;
+ };
+ DEF(Y() <=> Y());
+ static_assert(!noexcept(Y() <=> Y()));
+
+ struct ThrowingCmpCat {
+ ThrowingCmpCat(std::strong_ordering);
+ operator std::strong_ordering();
+ };
+ bool operator!=(ThrowingCmpCat o, int n) noexcept;
+
+ struct A {
+ friend ThrowingCmpCat operator<=>(A, A) noexcept;
+ };
+
+ struct B {
+ A a;
+ std::strong_ordering operator<=>(const B&) const = default;
+ };
+ DEF(B() <=> B());
+ static_assert(!noexcept(B() <=> B()));
+
+ struct C {
+ int n;
+ ThrowingCmpCat operator<=>(const C&) const = default;
+ };
+ DEF(C() <=> C());
+ static_assert(!noexcept(C() <=> C()));
+
+ struct D {
+ int n;
+ std::strong_ordering operator<=>(const D&) const = default;
+ };
+ DEF(D() <=> D());
+ static_assert(noexcept(D() <=> D()));
+
+
+ struct ThrowingCmpCat2 {
+ ThrowingCmpCat2(std::strong_ordering) noexcept;
+ operator std::strong_ordering() noexcept;
+ };
+ bool operator!=(ThrowingCmpCat2 o, int n);
+
+ struct E {
+ friend ThrowingCmpCat2 operator<=>(E, E) noexcept;
+ };
+
+ struct F {
+ E e;
+ std::strong_ordering operator<=>(const F&) const = default;
+ };
+ DEF(F() <=> F());
+ static_assert(noexcept(F() <=> F()));
+
+ struct G {
+ int n;
+ ThrowingCmpCat2 operator<=>(const G&) const = default;
+ };
+ DEF(G() <=> G());
+ static_assert(!noexcept(G() <=> G()));
+}
+
+namespace Synth {
+ struct A {
+ friend bool operator==(A, A) noexcept;
+ friend bool operator<(A, A) noexcept;
+ };
+ struct B {
+ A a;
+ friend std::strong_ordering operator<=>(B, B) = default;
+ };
+ std::strong_ordering operator<=>(B, B) noexcept;
+
+ struct C {
+ friend bool operator==(C, C);
+ friend bool operator<(C, C) noexcept;
+ };
+ struct D {
+ C c;
+ friend std::strong_ordering operator<=>(D, D) = default; // expected-note {{previous}}
+ };
+ std::strong_ordering operator<=>(D, D) noexcept; // expected-error {{does not match}}
+
+ struct E {
+ friend bool operator==(E, E) noexcept;
+ friend bool operator<(E, E);
+ };
+ struct F {
+ E e;
+ friend std::strong_ordering operator<=>(F, F) = default; // expected-note {{previous}}
+ };
+ std::strong_ordering operator<=>(F, F) noexcept; // expected-error {{does not match}}
+}
+
+namespace Secondary {
+ struct A {
+ friend bool operator==(A, A);
+ friend bool operator!=(A, A) = default; // expected-note {{previous}}
+
+ friend int operator<=>(A, A);
+ friend bool operator<(A, A) = default; // expected-note {{previous}}
+ friend bool operator<=(A, A) = default; // expected-note {{previous}}
+ friend bool operator>(A, A) = default; // expected-note {{previous}}
+ friend bool operator>=(A, A) = default; // expected-note {{previous}}
+ };
+ bool operator!=(A, A) noexcept; // expected-error {{does not match}}
+ bool operator<(A, A) noexcept; // expected-error {{does not match}}
+ bool operator<=(A, A) noexcept; // expected-error {{does not match}}
+ bool operator>(A, A) noexcept; // expected-error {{does not match}}
+ bool operator>=(A, A) noexcept; // expected-error {{does not match}}
+
+ struct B {
+ friend bool operator==(B, B) noexcept;
+ friend bool operator!=(B, B) = default;
+
+ friend int operator<=>(B, B) noexcept;
+ friend bool operator<(B, B) = default;
+ friend bool operator<=(B, B) = default;
+ friend bool operator>(B, B) = default;
+ friend bool operator>=(B, B) = default;
+ };
+ bool operator!=(B, B) noexcept;
+ bool operator<(B, B) noexcept;
+ bool operator<=(B, B) noexcept;
+ bool operator>(B, B) noexcept;
+ bool operator>=(B, B) noexcept;
+}
+
+// Check that we attempt to define a defaulted comparison before trying to
+// compute its exception specification.
+namespace DefineBeforeComputingExceptionSpec {
+ template<int> struct A {
+ A();
+ A(const A&) = delete; // expected-note 3{{here}}
+ friend bool operator==(A, A); // expected-note 3{{passing}}
+ friend bool operator!=(const A&, const A&) = default; // expected-error 3{{call to deleted constructor}}
+ };
+
+ bool a0 = A<0>() != A<0>(); // expected-note {{in defaulted equality comparison operator}}
+ bool a1 = operator!=(A<1>(), A<1>()); // expected-note {{in defaulted equality comparison operator}}
+
+ template struct A<2>;
+ bool operator!=(const A<2>&, const A<2>&) noexcept; // expected-note {{in evaluation of exception specification}}
+
+ template<int> struct B {
+ B();
+ B(const B&) = delete; // expected-note 3{{here}}
+ friend bool operator==(B, B); // expected-note 3{{passing}}
+ bool operator!=(const B&) const = default; // expected-error 3{{call to deleted constructor}}
+ };
+
+ bool b0 = B<0>() != B<0>(); // expected-note {{in defaulted equality comparison operator}}
+ bool b1 = B<1>().operator!=(B<1>()); // expected-note {{in defaulted equality comparison operator}}
+ int b2 = sizeof(&B<2>::operator!=); // expected-note {{in evaluation of exception specification}}
+}