Skip to content

[clang] Emit error for invalid friend functions under [temp.friend]p9 #78083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 17, 2024

Conversation

antangelo
Copy link
Contributor

Emits an error for friend FunctionDecls that either:

  • are not templates and have a requires clause
  • are templates, and have a constrained parameter that depends on a template parameter from an enclosing template

and are not a definition.

For a non-template friend function with a requires clause, if the function is not templated then the original error message indicating that such a function is disallowed is shown instead, as the function will still be rejected if a definition is added.

Emits an error for friend FunctionDecls that either:

* are not templates and have a requires clause
* are templates, and have a constrained parameter that depends on a
  template parameter from an enclosing template

and are not a definition.

For a non-template friend function with a requires clause, if the
function is not templated then the original error message indicating
that such a function is disallowed is shown instead, as the function
will still be rejected if a definition is added.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 14, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 14, 2024

@llvm/pr-subscribers-clang

Author: None (antangelo)

Changes

Emits an error for friend FunctionDecls that either:

  • are not templates and have a requires clause
  • are templates, and have a constrained parameter that depends on a template parameter from an enclosing template

and are not a definition.

For a non-template friend function with a requires clause, if the function is not templated then the original error message indicating that such a function is disallowed is shown instead, as the function will still be rejected if a definition is added.


Full diff: https://github.com/llvm/llvm-project/pull/78083.diff

6 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+5)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+33-8)
  • (modified) clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp (+1-1)
  • (modified) clang/test/SemaTemplate/GH71595.cpp (+4-4)
  • (modified) clang/test/SemaTemplate/concepts-friends.cpp (+13)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3cbce1be1594376..a9b9ff9dcc672c9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -560,6 +560,9 @@ Improvements to Clang's diagnostics
 - Clang now diagnoses unexpanded packs within the template argument lists of function template specializations.
 - Clang now diagnoses attempts to bind a bitfield to an NTTP of a reference type as erroneous
   converted constant expression and not as a reference to subobject.
+- Clang now diagnoses the requirement that non-template friend declarations with requires clauses
+  and template friend declarations with a constraint that depends on a template parameter from an
+  enclosing template must be a definition.
 
 
 Improvements to Clang's time-trace
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1a79892e40030ae..d08ef92bb614f81 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7000,6 +7000,11 @@ def err_member_decl_does_not_match : Error<
   "does not match any declaration in %1">;
 def err_friend_decl_with_def_arg_must_be_def : Error<
   "friend declaration specifying a default argument must be a definition">;
+def err_friend_decl_with_enclosing_temp_constraint_must_be_def : Error<
+  "friend declaration with a constraint that depends on an enclosing "
+  "template parameter must be a definition">;
+def err_non_temp_friend_decl_with_requires_clause_must_be_def : Error<
+  "non-template friend declaration with a requires clause must be a definition">;
 def err_friend_decl_with_def_arg_redeclared : Error<
   "friend declaration specifying a default argument must be the only declaration">;
 def err_friend_decl_does_not_match : Error<
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index e92fd104d78eb57..3b913cfe6cbb6cf 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10847,9 +10847,19 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     // Precalculate whether this is a friend function template with a constraint
     // that depends on an enclosing template, per [temp.friend]p9.
     if (isFriend && FunctionTemplate &&
-        FriendConstraintsDependOnEnclosingTemplate(NewFD))
+        FriendConstraintsDependOnEnclosingTemplate(NewFD)) {
       NewFD->setFriendConstraintRefersToEnclosingTemplate(true);
 
+      // C++ [temp.friend]p9:
+      //    A friend function template with a constraint that depends on a
+      //    template parameter from an enclosing template shall be a definition.
+      if (!D.isFunctionDefinition()) {
+        Diag(NewFD->getBeginLoc(),
+             diag::err_friend_decl_with_enclosing_temp_constraint_must_be_def);
+        NewFD->setInvalidDecl();
+      }
+    }
+
     if (FunctionTemplate) {
       if (NewFD->isInvalidDecl())
         FunctionTemplate->setInvalidDecl();
@@ -12066,11 +12076,12 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
         checkThisInStaticMemberFunctionType(Method);
     }
 
-    // C++20: dcl.decl.general p4:
-    // The optional requires-clause ([temp.pre]) in an init-declarator or
-    // member-declarator shall be present only if the declarator declares a
-    // templated function ([dcl.fct]).
     if (Expr *TRC = NewFD->getTrailingRequiresClause()) {
+      // C++20: dcl.decl.general p4:
+      // The optional requires-clause ([temp.pre]) in an init-declarator or
+      // member-declarator shall be present only if the declarator declares a
+      // templated function ([dcl.fct]).
+      //
       // [temp.pre]/8:
       // An entity is templated if it is
       // - a template,
@@ -12088,15 +12099,29 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
       // templated. A templated variable is a variable template or a variable
       // that is templated.
 
-      if (!NewFD->getDescribedFunctionTemplate() && // -a template
-          // defined... in a templated entity
+      bool IsTemplate = NewFD->getDescribedFunctionTemplate();
+      bool IsFriend = NewFD->getFriendObjectKind();
+      if (!IsTemplate && // -a template
+                         // defined... in a templated entity
           !(DeclIsDefn && NewFD->isTemplated()) &&
           // a member of a templated entity
           !(isa<CXXMethodDecl>(NewFD) && NewFD->isTemplated()) &&
           // Don't complain about instantiations, they've already had these
           // rules + others enforced.
-          !NewFD->isTemplateInstantiation()) {
+          !NewFD->isTemplateInstantiation() &&
+          // If the function violates [temp.friend]p9 because it is missing
+          // a definition, and adding a definition would make it templated,
+          // then let that error take precedence.
+          !(!DeclIsDefn && IsFriend && NewFD->isTemplated())) {
         Diag(TRC->getBeginLoc(), diag::err_constrained_non_templated_function);
+      } else if (!DeclIsDefn && !IsTemplate && IsFriend &&
+                 !NewFD->isTemplateInstantiation()) {
+        // C++ [temp.friend]p9:
+        //   A non-template friend declaration with a requires-clause shall be a
+        //   definition.
+        Diag(NewFD->getBeginLoc(),
+             diag::err_non_temp_friend_decl_with_requires_clause_must_be_def);
+        NewFD->setInvalidDecl();
       }
     }
 
diff --git a/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp b/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp
index 83ec78dac9fe3f8..c7596218db5379f 100644
--- a/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp
+++ b/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp
@@ -27,7 +27,7 @@ auto *p = new void(*)(char)
 namespace GH61748 {
 template<typename T>
 struct S {
-  // expected-error@+1 {{non-templated function cannot have a requires clause}}
+  // expected-error@+1 {{non-template friend declaration with a requires clause must be a definition}}
   friend void declared_friend() requires(sizeof(T) > 1);
   // OK, is a definition.
   friend void defined_friend() requires(sizeof(T) > 1){}
diff --git a/clang/test/SemaTemplate/GH71595.cpp b/clang/test/SemaTemplate/GH71595.cpp
index 7d34d1bf054e4bc..daec9410e547a6b 100644
--- a/clang/test/SemaTemplate/GH71595.cpp
+++ b/clang/test/SemaTemplate/GH71595.cpp
@@ -18,17 +18,17 @@ void f() {
 template<class A>
 class temp {
     template<C<temp> T>
-    friend void g();
+    friend void g(); // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}}
 
-    temp(); // expected-note {{implicitly declared private here}}
+    temp();
 };
 
 template<C<temp<int>> T>
 void g() {
-    auto v = temp<T>(); // expected-error {{calling a private constructor of class 'temp<int>'}}
+    auto v = temp<T>();
 }
 
 void h() {
     f<int>();
-    g<int>(); // expected-note {{in instantiation of function template specialization 'g<int>' requested here}}
+    g<int>();
 }
diff --git a/clang/test/SemaTemplate/concepts-friends.cpp b/clang/test/SemaTemplate/concepts-friends.cpp
index 5c4609520a3c7ea..255b0858917fb6f 100644
--- a/clang/test/SemaTemplate/concepts-friends.cpp
+++ b/clang/test/SemaTemplate/concepts-friends.cpp
@@ -2,6 +2,7 @@
 
 template <typename T>
 concept constraint = false;
+
 namespace temp_friend_9 {
 // A non-template friend declaration with a requires-clause shall be a
 // definition. ...Such a constrained friend function ... does not declare the
@@ -11,6 +12,14 @@ struct NonTemplateFriend {
   friend void foo()
     requires true
   {}
+
+  friend void baz() // expected-error {{non-template friend declaration with a requires clause must be a definition}}
+    requires true;
+};
+
+struct TempP9NotShownIfFunctionWouldBeInvalidAnyway {
+  friend void foo()
+    requires true; // expected-error {{non-templated function cannot have a requires clause}}
 };
 
 // A friend function template with a constraint that depends on a template
@@ -19,6 +28,10 @@ struct NonTemplateFriend {
 // function template as a declaration in any other scope.
 template <typename T>
 struct TemplateFromEnclosing {
+  template <typename U>
+  friend void bar2() // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}}
+    requires constraint<T>;
+
   template <typename U>
   friend void foo()
     requires constraint<T>

@antangelo antangelo merged commit 46a395d into llvm:main Jan 17, 2024
@antangelo antangelo deleted the temp-friend-p9-error branch January 17, 2024 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants