diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index bc9c422ed4c47..b295db62bf016 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5026,6 +5026,11 @@ class Sema final : public SemaBase { /// special member function. void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD); + bool AreExceptionSpecsEqual(const NamedDecl *Old, + const Expr *OldExceptionSpec, + const NamedDecl *New, + const Expr *NewExceptionSpec); + /// Check the given exception-specification and update the /// exception specification information with the results. void checkExceptionSpecification(bool IsTopLevel, diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index dbddd6c370aa0..a532c0a2c84e0 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -#include "clang/Sema/SemaInternal.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Expr.h" @@ -19,6 +18,9 @@ #include "clang/AST/TypeLoc.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceManager.h" +#include "clang/Sema/EnterExpressionEvaluationContext.h" +#include "clang/Sema/SemaInternal.h" +#include "clang/Sema/Template.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include @@ -314,6 +316,22 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { return false; } + if (Old->getExceptionSpecType() == EST_DependentNoexcept && + New->getExceptionSpecType() == EST_DependentNoexcept) { + const auto *OldType = Old->getType()->getAs(); + const auto *NewType = New->getType()->getAs(); + OldType = ResolveExceptionSpec(Old->getLocation(), OldType); + if (!OldType) + return false; + NewType = ResolveExceptionSpec(New->getLocation(), NewType); + if (!NewType) + return false; + + if (AreExceptionSpecsEqual(Old, OldType->getNoexceptExpr(), New, + NewType->getNoexceptExpr())) + return false; + } + // Check the types as written: they must match before any exception // specification adjustment is applied. if (!CheckEquivalentExceptionSpecImpl( @@ -501,6 +519,110 @@ bool Sema::CheckEquivalentExceptionSpec( return Result; } +static const Expr *SubstituteExceptionSpecWithoutEvaluation( + Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo, + const Expr *ExceptionSpec) { + MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( + DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false, + /*Innermost=*/std::nullopt, + /*RelativeToPrimary=*/true, + /*ForConstraintInstantiation=*/true); + + if (MLTAL.getNumSubstitutedLevels() == 0) + return ExceptionSpec; + + Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false); + + auto *FD = const_cast(DeclInfo.getDecl()->getAsFunction()); + Sema::InstantiatingTemplate Inst( + S, DeclInfo.getLocation(), FD, + Sema::InstantiatingTemplate::ExceptionSpecification()); + if (Inst.isInvalid()) + return nullptr; + + // Set up a dummy 'instantiation' scope in the case of reference to function + // parameters that the surrounding function hasn't been instantiated yet. Note + // this may happen while we're comparing two templates' constraint + // equivalence. + LocalInstantiationScope ScopeForParameters(S); + + for (auto *PVD : FD->parameters()) { + if (!PVD->isParameterPack()) { + ScopeForParameters.InstantiatedLocal(PVD, PVD); + continue; + } + // This is hacky: we're mapping the parameter pack to a size-of-1 argument + // to avoid building SubstTemplateTypeParmPackTypes for + // PackExpansionTypes. The SubstTemplateTypeParmPackType node would + // otherwise reference the AssociatedDecl of the template arguments, which + // is, in this case, the template declaration. + // + // However, as we are in the process of comparing potential + // re-declarations, the canonical declaration is the declaration itself at + // this point. So if we didn't expand these packs, we would end up with an + // incorrect profile difference because we will be profiling the + // canonical types! + // + // FIXME: Improve the "no-transform" machinery in FindInstantiatedDecl so + // that we can eliminate the Scope in the cases where the declarations are + // not necessarily instantiated. It would also benefit the noexcept + // specifier comparison. + ScopeForParameters.MakeInstantiatedLocalArgPack(PVD); + ScopeForParameters.InstantiatedLocalPackArg(PVD, PVD); + } + + // See TreeTransform::RebuildTemplateSpecializationType. A context scope is + // essential for having an injected class as the canonical type for a template + // specialization type at the rebuilding stage. This guarantees that, for + // out-of-line definitions, injected class name types and their equivalent + // template specializations can be profiled to the same value, which makes it + // possible that e.g. constraints involving C> and C are + // perceived identical. + Sema::ContextRAII ContextScope(S, FD); + + auto *MD = dyn_cast(FD); + Sema::CXXThisScopeRAII ThisScope( + S, MD ? MD->getParent() : nullptr, + MD ? MD->getMethodQualifiers() : Qualifiers{}, MD != nullptr); + + EnterExpressionEvaluationContext ConstantEvaluated( + S, Sema::ExpressionEvaluationContext::ConstantEvaluated); + + ExprResult SubstExceptionSpec = + S.SubstExpr(const_cast(ExceptionSpec), MLTAL); + if (SFINAE.hasErrorOccurred() || !SubstExceptionSpec.isUsable()) + return nullptr; + return SubstExceptionSpec.get(); +} + +bool Sema::AreExceptionSpecsEqual(const NamedDecl *Old, + const Expr *OldExceptionSpec, + const NamedDecl *New, + const Expr *NewExceptionSpec) { + if (OldExceptionSpec == NewExceptionSpec) + return true; + if (Old && New && + Old->getLexicalDeclContext() != New->getLexicalDeclContext()) { + if (const Expr *SubstExceptionSpec = + SubstituteExceptionSpecWithoutEvaluation(*this, Old, + OldExceptionSpec)) + OldExceptionSpec = SubstExceptionSpec; + else + return false; + if (const Expr *SubstExceptionSpec = + SubstituteExceptionSpecWithoutEvaluation(*this, New, + NewExceptionSpec)) + NewExceptionSpec = SubstExceptionSpec; + else + return false; + } + + llvm::FoldingSetNodeID ID1, ID2; + OldExceptionSpec->Profile(ID1, Context, /*Canonical=*/true); + NewExceptionSpec->Profile(ID2, Context, /*Canonical=*/true); + return ID1 == ID2; +} + /// CheckEquivalentExceptionSpec - Check if the two types have compatible /// exception specifications. See C++ [except.spec]p3. /// @@ -574,17 +696,6 @@ static bool CheckEquivalentExceptionSpecImpl( } } - // C++14 [except.spec]p3: - // Two exception-specifications are compatible if [...] both have the form - // noexcept(constant-expression) and the constant-expressions are equivalent - if (OldEST == EST_DependentNoexcept && NewEST == EST_DependentNoexcept) { - llvm::FoldingSetNodeID OldFSN, NewFSN; - Old->getNoexceptExpr()->Profile(OldFSN, S.Context, true); - New->getNoexceptExpr()->Profile(NewFSN, S.Context, true); - if (OldFSN == NewFSN) - return false; - } - // Dynamic exception specifications with the same set of adjusted types // are compatible. if (OldEST == EST_Dynamic && NewEST == EST_Dynamic) { diff --git a/clang/test/CXX/basic/basic.link/p11.cpp b/clang/test/CXX/basic/basic.link/p11.cpp new file mode 100644 index 0000000000000..e244336417fd6 --- /dev/null +++ b/clang/test/CXX/basic/basic.link/p11.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s + +namespace MemberSpecialization { + template + struct A { + template + void f() noexcept(B); + + template + void g() noexcept(B); // expected-note {{previous declaration is here}} + }; + + template<> + template + void A::f() noexcept(B); + + template<> + template + void A::g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}} +} + +namespace Friend { + template + void f() noexcept(B); + + template + void g() noexcept(B); // expected-note {{previous declaration is here}} + + template + struct A { + template + friend void f() noexcept(B); + + template + friend void g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}} + }; +}