[clang] [Clang][Parse] Delay parsing of noexcept-specifiers in friend function declarations (PR #90517)

Krystian Stasiowski via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 30 05:57:25 PDT 2024


https://github.com/sdkrystian updated https://github.com/llvm/llvm-project/pull/90517

>From 3f5feb2b17c06f6e001e9324e90eef07fd720539 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Mon, 29 Apr 2024 15:34:40 -0400
Subject: [PATCH 1/3] [Clang][Parse] Delay parsing of noexcept-specifiers in
 friend function declarations

---
 clang/lib/Parse/ParseDecl.cpp | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index a7846e102a43c7..93950e27a08f35 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -7388,12 +7388,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) &&

>From a327d79594257daee50643a83edbbd62037736de Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 30 Apr 2024 07:44:22 -0400
Subject: [PATCH 2/3] [FOLD] fix delayed parsing of exception specification for
 friend functions

---
 clang/lib/Sema/SemaDeclCXX.cpp                | 38 ++++-----
 clang/lib/Sema/SemaExceptionSpec.cpp          | 13 ++--
 .../class/class.mem/class.mem.general/p8.cpp  | 78 +++++++++++++++++++
 .../CXX/except/except.spec/p13-friend.cpp     | 29 +++++++
 4 files changed, 133 insertions(+), 25 deletions(-)
 create mode 100644 clang/test/CXX/class/class.mem/class.mem.general/p8.cpp
 create mode 100644 clang/test/CXX/except/except.spec/p13-friend.cpp

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 338b0ec1e099c0..e7a8c9ed078bfa 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -19172,21 +19172,19 @@ 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.
@@ -19197,15 +19195,17 @@ void Sema::actOnDelayedExceptionSpecification(Decl *MethodD,
                               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 at -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

>From e7bc177cbefad5ca56b087a1bdf87938b8597152 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 30 Apr 2024 08:57:13 -0400
Subject: [PATCH 3/3] [FOLD] add release note

---
 clang/docs/ReleaseNotes.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 604782ca43dd54..5e6d16708cc0b8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -596,6 +596,7 @@ Bug Fixes to C++ Support
   immediate function context.
 - Fix CTAD for ``std::initializer_list``. This allows ``std::initializer_list{1, 2, 3}`` to be deduced as
   ``std::initializer_list<int>`` as intended.
+- Clang now correctly treats the noexcept-specifier of a friend function to be a complete-class context.
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^



More information about the cfe-commits mailing list