Author: Krystian Stasiowski Date: 2024-04-30T14:23:02-04:00 New Revision: f061a395ffb78215a23e0f503e8ea121ee3b13ad
URL: https://github.com/llvm/llvm-project/commit/f061a395ffb78215a23e0f503e8ea121ee3b13ad DIFF: https://github.com/llvm/llvm-project/commit/f061a395ffb78215a23e0f503e8ea121ee3b13ad.diff LOG: [Clang][Sema][Parse] Delay parsing of noexcept-specifiers in friend function declarations (#90517) According to [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. When testing #90152, it came to my attention that we do _not_ consider the _noexcept-specifier_ of a friend function declaration to be a complete-class context (something which the Microsoft standard library depends on). Although a comment states that this is "consistent with what other implementations do", the only other implementation that exhibits this behavior is GCC (MSVC and EDG both late-parse the _noexcept-specifier_). This patch changes _noexcept-specifiers_ of friend function declarations to be late parsed, which is in agreement with the standard & majority of implementations. Pre-#90152, our existing implementation falls "in between" the implementation consensus: within non-template classes, we would not find latter declared members (qualified and unqualified), while within class templates we would not find latter declared member when named with a unqualified name, we would find members named with a qualified name (even when lookup context is the current instantiation). Therefore, this _shouldn't_ be a breaking change -- any code that didn't compile will continue to not compile (since a _noexcept-specifier_ is not part of the deduction substitution loci (see [temp.deduct.general] p7), and any code which did compile should continue to do so. Added: clang/test/CXX/class/class.mem/class.mem.general/p8.cpp clang/test/CXX/except/except.spec/p13-friend.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Sema/Sema.h clang/lib/Parse/ParseDecl.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExceptionSpec.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a90df458705736..7458ddc99d4eb5 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 809b9c4498f697..72181c9ddae7c5 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<QualType> &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<ParsedType> DynamicExceptions, + Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange, + ArrayRef<ParsedType> DynamicExceptions, ArrayRef<SourceRange> DynamicExceptionRanges, Expr *NoexceptExpr); class InheritedConstructorInfo; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 7431c256d2c135..69fabf6acb84e6 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -7416,12 +7416,20 @@ void Parser::ParseFunctionDeclarator(Declarator &D, std::optional<Sema::CXXThisScopeRAII> 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 338b0ec1e099c0..83155c3216916e 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<ParsedType> DynamicExceptions, - ArrayRef<SourceRange> DynamicExceptionRanges, - Expr *NoexceptExpr) { - if (!MethodD) +void Sema::actOnDelayedExceptionSpecification( + Decl *D, ExceptionSpecificationType EST, SourceRange SpecificationRange, + ArrayRef<ParsedType> DynamicExceptions, + ArrayRef<SourceRange> DynamicExceptionRanges, Expr *NoexceptExpr) { + if (!D) return; - // Dig out the method we're referring to. - if (FunctionTemplateDecl *FunTmpl = dyn_cast<FunctionTemplateDecl>(MethodD)) - MethodD = FunTmpl->getTemplatedDecl(); + // Dig out the function we're referring to. + if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(D)) + D = FTD->getTemplatedDecl(); - CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(MethodD); - if (!Method) + FunctionDecl *FD = dyn_cast<FunctionDecl>(D); + if (!FD) return; // Check the exception specification. llvm::SmallVector<QualType, 4> 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<CXXMethodDecl>(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 c9dd6bb2413e38..41bf273d12f2f3 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<CXXMethodDecl>(FD); - if (!MD) + ExceptionSpecificationType EST = + FD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType(); + if (EST == EST_Unparsed) + return true; + else if (EST != EST_Unevaluated) return false; - - auto EST = MD->getType()->castAs<FunctionProtoType>()->getExceptionSpecType(); - return EST == EST_Unparsed || - (EST == EST_Unevaluated && MD->getParent()->isBeingDefined()); + const DeclContext *DC = FD->getLexicalDeclContext(); + return DC->isRecord() && cast<RecordDecl>(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 00000000000000..8cc9b41eaca919 --- /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<typename T> + void f2() noexcept(x); + template<typename T> + void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template<typename T> + void f3() noexcept(A::x); + template<typename T> + 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<typename T> + friend void f6() noexcept(x); + template<typename T> + friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template<typename T> + friend void f7() noexcept(A::x); + template<typename T> + 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<typename T> + 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<T>'}} + + template<typename U> + void f2() noexcept(x); + template<typename U> + void g2() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template<typename U> + void f3() noexcept(A::x); + template<typename U> + void g3() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}} + + 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<T>'}} + + template<typename U> + friend void f6() noexcept(x); + template<typename U> + friend void g6() noexcept(y); // expected-error {{use of undeclared identifier 'y'}} + + template<typename U> + friend void f7() noexcept(A::x); + template<typename U> + friend void g7() noexcept(A::y); // expected-error {{no member named 'y' in 'A<T>'}} + + 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 00000000000000..7f73a4ff431aa9 --- /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<typename T> + 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<int>'; 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<int>; // expected-note {{in instantiation of template class}} +} // namespace N1 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits