diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a90df45870573..7458ddc99d4eb 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -614,6 +614,7 @@ Bug Fixes to C++ Support - Fix a bug on template partial specialization whose template parameter is `decltype(auto)`. - Fix a bug on template partial specialization with issue on deduction of nontype template parameter whose type is `decltype(auto)`. Fixes (#GH68885). +- Clang now correctly treats the noexcept-specifier of a friend function to be a complete-class context. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 809b9c4498f69..72181c9ddae7c 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4097,12 +4097,12 @@ class Sema final : public SemaBase { SmallVectorImpl &Exceptions, FunctionProtoType::ExceptionSpecInfo &ESI); - /// Add an exception-specification to the given member function - /// (or member function template). The exception-specification was parsed - /// after the method itself was declared. + /// Add an exception-specification to the given member or friend function + /// (or function template). The exception-specification was parsed + /// after the function itself was declared. void actOnDelayedExceptionSpecification( - Decl *Method, ExceptionSpecificationType EST, - SourceRange SpecificationRange, ArrayRef DynamicExceptions, + Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange, + ArrayRef DynamicExceptions, ArrayRef DynamicExceptionRanges, Expr *NoexceptExpr); class InheritedConstructorInfo; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 7431c256d2c13..69fabf6acb84e 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -7416,12 +7416,20 @@ void Parser::ParseFunctionDeclarator(Declarator &D, std::optional ThisScope; InitCXXThisScopeForDeclaratorIfRelevant(D, DS, ThisScope); - // Parse exception-specification[opt]. - // FIXME: Per [class.mem]p6, all exception-specifications at class scope - // should be delayed, including those for non-members (eg, friend - // declarations). But only applying this to member declarations is - // consistent with what other implementations do. - bool Delayed = D.isFirstDeclarationOfMember() && + // C++ [class.mem.general]p8: + // A complete-class context of a class (template) is a + // - function body, + // - default argument, + // - default template argument, + // - noexcept-specifier, or + // - default member initializer + // within the member-specification of the class or class template. + // + // Parse exception-specification[opt]. If we are in the + // member-specification of a class or class template, this is a + // complete-class context and parsing of the noexcept-specifier should be + // delayed (even if this is a friend declaration). + bool Delayed = D.getContext() == DeclaratorContext::Member && D.isFunctionDeclaratorAFunctionDeclaration(); if (Delayed && Actions.isLibstdcxxEagerExceptionSpecHack(D) && GetLookAheadToken(0).is(tok::kw_noexcept) && diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 338b0ec1e099c..83155c3216916 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -19172,40 +19172,40 @@ void Sema::checkExceptionSpecification( } } -void Sema::actOnDelayedExceptionSpecification(Decl *MethodD, - ExceptionSpecificationType EST, - SourceRange SpecificationRange, - ArrayRef DynamicExceptions, - ArrayRef DynamicExceptionRanges, - Expr *NoexceptExpr) { - if (!MethodD) +void Sema::actOnDelayedExceptionSpecification( + Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange, + ArrayRef DynamicExceptions, + ArrayRef DynamicExceptionRanges, Expr *NoexceptExpr) { + if (!D) return; - // Dig out the method we're referring to. - if (FunctionTemplateDecl *FunTmpl = dyn_cast(MethodD)) - MethodD = FunTmpl->getTemplatedDecl(); + // Dig out the function we're referring to. + if (FunctionTemplateDecl *FTD = dyn_cast(D)) + D = FTD->getTemplatedDecl(); - CXXMethodDecl *Method = dyn_cast(MethodD); - if (!Method) + FunctionDecl *FD = dyn_cast(D); + if (!FD) return; // Check the exception specification. llvm::SmallVector Exceptions; FunctionProtoType::ExceptionSpecInfo ESI; - checkExceptionSpecification(/*IsTopLevel*/true, EST, DynamicExceptions, + checkExceptionSpecification(/*IsTopLevel=*/true, EST, DynamicExceptions, DynamicExceptionRanges, NoexceptExpr, Exceptions, ESI); // Update the exception specification on the function type. - Context.adjustExceptionSpec(Method, ESI, /*AsWritten*/true); + Context.adjustExceptionSpec(FD, ESI, /*AsWritten=*/true); - if (Method->isStatic()) - checkThisInStaticMemberFunctionExceptionSpec(Method); + if (CXXMethodDecl *MD = dyn_cast(D)) { + if (MD->isStatic()) + checkThisInStaticMemberFunctionExceptionSpec(MD); - if (Method->isVirtual()) { - // Check overrides, which we previously had to delay. - for (const CXXMethodDecl *O : Method->overridden_methods()) - CheckOverridingFunctionExceptionSpec(Method, O); + if (MD->isVirtual()) { + // Check overrides, which we previously had to delay. + for (const CXXMethodDecl *O : MD->overridden_methods()) + CheckOverridingFunctionExceptionSpec(MD, O); + } } } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index c9dd6bb2413e3..41bf273d12f2f 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -258,13 +258,14 @@ Sema::UpdateExceptionSpec(FunctionDecl *FD, } static bool exceptionSpecNotKnownYet(const FunctionDecl *FD) { - auto *MD = dyn_cast(FD); - if (!MD) + ExceptionSpecificationType EST = + FD->getType()->castAs()->getExceptionSpecType(); + if (EST == EST_Unparsed) + return true; + else if (EST != EST_Unevaluated) return false; - - auto EST = MD->getType()->castAs()->getExceptionSpecType(); - return EST == EST_Unparsed || - (EST == EST_Unevaluated && MD->getParent()->isBeingDefined()); + const DeclContext *DC = FD->getLexicalDeclContext(); + return DC->isRecord() && cast(DC)->isBeingDefined(); } static bool CheckEquivalentExceptionSpecImpl( diff --git a/clang/test/CXX/class/class.mem/class.mem.general/p8.cpp b/clang/test/CXX/class/class.mem/class.mem.general/p8.cpp new file mode 100644 index 0000000000000..8cc9b41eaca91 --- /dev/null +++ b/clang/test/CXX/class/class.mem/class.mem.general/p8.cpp @@ -0,0 +1,78 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +namespace N0 { + struct A { + void f0() noexcept(x); + void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + void f1() noexcept(A::x); + void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}} + + template + void f2() noexcept(x); + template + void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template + void f3() noexcept(A::x); + template + void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}} + + friend void f4() noexcept(x); + friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + friend void f5() noexcept(A::x); + friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}} + + template + friend void f6() noexcept(x); + template + friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template + friend void f7() noexcept(A::x); + template + friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'N0::A'}} + + static constexpr bool x = true; + }; +} // namespace N0 + +namespace N1 { + template + struct A { + void f0() noexcept(x); + void g0() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + void f1() noexcept(A::x); + void g1() noexcept(A::y); // expected-error {{no member named 'y' in 'A'}} + + template + void f2() noexcept(x); + template + void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template + void f3() noexcept(A::x); + template + void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'A'}} + + friend void f4() noexcept(x); + friend void g4() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + friend void f5() noexcept(A::x); + friend void g5() noexcept(A::y); // expected-error {{no member named 'y' in 'A'}} + + template + friend void f6() noexcept(x); + template + friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template + friend void f7() noexcept(A::x); + template + friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'A'}} + + static constexpr bool x = true; + }; +} // namespace N1 diff --git a/clang/test/CXX/except/except.spec/p13-friend.cpp b/clang/test/CXX/except/except.spec/p13-friend.cpp new file mode 100644 index 0000000000000..7f73a4ff431aa --- /dev/null +++ b/clang/test/CXX/except/except.spec/p13-friend.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s + +namespace N0 { + void f() noexcept; + void g() noexcept; + + struct A { + friend void f() noexcept; + friend void g() noexcept(x); + + static constexpr bool x = true; + }; +} // namespace N0 + +namespace N1 { + void f() noexcept; + void g(); + + template + struct A { + friend void f() noexcept; + // FIXME: This error is emitted if no other errors occured (i.e. Sema::hasUncompilableErrorOccurred() is false). + friend void g() noexcept(x); // expected-error {{no member 'x' in 'N1::A'; it has not yet been instantiated}} + // expected-note@-1 {{in instantiation of exception specification}} + static constexpr bool x = false; // expected-note {{not-yet-instantiated member is declared here}} + }; + + template struct A; // expected-note {{in instantiation of template class}} +} // namespace N1