Improve requirement clause limitation on non templated function
authorErich Keane <erich.keane@intel.com>
Tue, 28 Mar 2023 18:12:50 +0000 (11:12 -0700)
committerErich Keane <erich.keane@intel.com>
Wed, 29 Mar 2023 13:27:25 +0000 (06:27 -0700)
The current implementation 6da3d66f03f9162ef341cc67218be40e22fe9808
got a few things wrong, particularly that a template, or  definition
or member in a templated entity is required to be allowed to have a
trailing requires clause.

This patch corrects this, as reproted by #61748

Fixes: #61748

Differential Revision: https://reviews.llvm.org/D147070

clang/docs/ReleaseNotes.rst
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaLambda.cpp
clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp
clang/test/Parser/cxx2b-lambdas.cpp
clang/test/SemaCXX/lambda-capture-type-deduction.cpp

index aabaab5..a41010d 100644 (file)
@@ -248,7 +248,11 @@ Bug Fixes in This Version
 - Fix false-positive diagnostic issued for consteval initializers of temporary
   objects.
   (`#60286 <https://github.com/llvm/llvm-project/issues/60286>`_)
-
+- Correct restriction of trailing requirements clauses on a templated function.
+  Previously we only rejected non-'templated' things, but the restrictions ALSO need
+  to limit non-defined/non-member functions as well. Additionally, we now diagnose
+  requires on lambdas when not allowed, which we previously missed.
+  (`#61748 <https://github.com/llvm/llvm-project/issues/61748>`_)
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index dd001db..7596b51 100644 (file)
@@ -11882,8 +11882,33 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
     // member-declarator shall be present only if the declarator declares a
     // templated function ([dcl.fct]).
     if (Expr *TRC = NewFD->getTrailingRequiresClause()) {
-      if (!NewFD->isTemplated() && !NewFD->isTemplateInstantiation())
+      // [temp.pre]/8:
+      // An entity is templated if it is
+      // - a template,
+      // - an entity defined ([basic.def]) or created ([class.temporary]) in a
+      // templated entity,
+      // - a member of a templated entity,
+      // - an enumerator for an enumeration that is a templated entity, or
+      // - the closure type of a lambda-expression ([expr.prim.lambda.closure])
+      // appearing in the declaration of a templated entity. [Note 6: A local
+      // class, a local or block variable, or a friend function defined in a
+      // templated entity is a templated entity.  — end note]
+      //
+      // A templated function is a function template or a function that is
+      // templated. A templated class is a class template or a class that is
+      // templated. A templated variable is a variable template or a variable
+      // that is templated.
+
+      if (!NewFD->getDescribedFunctionTemplate() && // -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()) {
         Diag(TRC->getBeginLoc(), diag::err_constrained_non_templated_function);
+      }
     }
 
     if (CXXConversionDecl *Conversion = dyn_cast<CXXConversionDecl>(NewFD))
index 64db9d0..b6a4f26 100644 (file)
@@ -1380,6 +1380,37 @@ void Sema::ActOnStartOfLambdaDefinition(LambdaIntroducer &Intro,
     PushOnScopeChains(P, CurScope);
   }
 
+  // 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 = Method->getTrailingRequiresClause()) {
+    // [temp.pre]/8:
+    // An entity is templated if it is
+    // - a template,
+    // - an entity defined ([basic.def]) or created ([class.temporary]) in a
+    // templated entity,
+    // - a member of a templated entity,
+    // - an enumerator for an enumeration that is a templated entity, or
+    // - the closure type of a lambda-expression ([expr.prim.lambda.closure])
+    // appearing in the declaration of a templated entity. [Note 6: A local
+    // class, a local or block variable, or a friend function defined in a
+    // templated entity is a templated entity.  — end note]
+    //
+    // A templated function is a function template or a function that is
+    // templated. A templated class is a class template or a class that is
+    // templated. A templated variable is a variable template or a variable
+    // that is templated.
+
+    // Note: we only have to check if this is defined in a template entity, OR
+    // if we are a template, since the rest don't apply. The requires clause
+    // applies to the call operator, which we already know is a member function,
+    // AND defined.
+    if (!Method->getDescribedFunctionTemplate() && !Method->isTemplated()) {
+      Diag(TRC->getBeginLoc(), diag::err_constrained_non_templated_function);
+    }
+  }
+
   // Enter a new evaluation context to insulate the lambda from any
   // cleanups from the enclosing full-expression.
   PushExpressionEvaluationContext(
index 3c012c8..83ec78d 100644 (file)
@@ -23,3 +23,41 @@ void g(int (*)() requires true);
 // expected-error@+1{{expected expression}}
 auto *p = new void(*)(char)
   requires true;
+
+namespace GH61748 {
+template<typename T>
+struct S {
+  // expected-error@+1 {{non-templated function cannot have a requires clause}}
+  friend void declared_friend() requires(sizeof(T) > 1);
+  // OK, is a definition.
+  friend void defined_friend() requires(sizeof(T) > 1){}
+  // OK, is a member.
+  void member() requires(sizeof(T) > 1);
+};
+
+template<typename T>
+void ContainingFunction() {
+  // expected-error@+1 {{non-templated function cannot have a requires clause}}
+  void bad() requires(sizeof(T) > 1);
+  // expected-error@+1 {{function definition is not allowed here}}
+  void still_bad() requires(sizeof(T) > 1) {}
+
+}
+
+void NonTemplContainingFunction() {
+  // expected-error@+1 {{non-templated function cannot have a requires clause}}
+  (void)[]() requires (sizeof(int)>1){};
+  // OK, a template.
+  auto X = [](auto) requires (sizeof(int)>1){};
+  // OK, a template.
+  auto Y = []<typename T>(T t) requires (sizeof(int)>1){};
+
+  X(1);
+  Y(1);
+}
+
+template<typename T>
+union U {
+  void f() requires true;
+};
+}
index 9474dfa..2de4b65 100644 (file)
@@ -18,7 +18,7 @@ auto L9 = []<typename T> { return true; };
 auto L10 = []<typename T> noexcept { return true; };
 auto L11 = []<typename T> -> bool { return true; };
 auto L12 = [] consteval {};
-auto L13 = []() requires true {};
+auto L13 = []() requires true {}; // expected-error{{non-templated function cannot have a requires clause}}
 auto L14 = []<auto> requires true() requires true {};
 auto L15 = []<auto> requires true noexcept {};
 auto L16 = [] [[maybe_unused]]{};
index e22e3af..8c9f6f6 100644 (file)
@@ -48,6 +48,7 @@ void test_noexcept() {
   static_assert(noexcept([&] mutable noexcept(is_same<int &, decltype((y))>) {}()));
 }
 
+template<typename T>
 void test_requires() {
 
   int x;
@@ -77,6 +78,10 @@ void test_requires() {
   [x = 1]() mutable requires is_same<int &, decltype((x))> {} ();
 }
 
+void use() {
+  test_requires<int>();
+}
+
 void err() {
   int y, z;
   (void)[x = 1]<typename T>