ilya-biryukov created this revision.
ilya-biryukov added reviewers: aaron.ballman, erichkeane.
Herald added a project: All.
ilya-biryukov requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

This commit implements [temp.deduct]p9.
Test updates include:

- New notes in `cxx1y-init-captures.cpp`, `lambda-expressions.cpp` and 
'warn-unused-lambda-capture.cpp'. This seems to be caused by diagnosing errors 
earlier (during deduction) that were previously surfaced later (during 
instantiation).
- New error `lambda-unevaluated.cpp` is in line with [temp.deduct]p9.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D148802

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Sema/Sema.h
  clang/lib/Frontend/FrontendActions.cpp
  clang/lib/Sema/SemaTemplateInstantiate.cpp
  clang/test/CXX/temp/temp.deduct/p9.cpp
  clang/test/SemaCXX/cxx1y-init-captures.cpp
  clang/test/SemaCXX/lambda-expressions.cpp
  clang/test/SemaCXX/lambda-unevaluated.cpp
  clang/test/SemaCXX/warn-unused-lambda-capture.cpp
  clang/www/cxx_status.html

Index: clang/www/cxx_status.html
===================================================================
--- clang/www/cxx_status.html
+++ clang/www/cxx_status.html
@@ -1024,11 +1024,7 @@
     <tr>
       <td>Lambdas in unevaluated contexts</td>
       <td><a href="https://wg21.link/p0315r4";>P0315R4</a></td>
-      <td class="partial" align="center">
-        <details><summary>Partial</summary>
-          temp.deduct/9 is not implemented yet.
-        </details>
-      </td>
+      <td class="full" align="center">Clang 17</td>
     </tr>
     <!-- Jacksonville papers -->
     <tr>
Index: clang/test/SemaCXX/warn-unused-lambda-capture.cpp
===================================================================
--- clang/test/SemaCXX/warn-unused-lambda-capture.cpp
+++ clang/test/SemaCXX/warn-unused-lambda-capture.cpp
@@ -189,7 +189,7 @@
 }
 
 void test_use_template() {
-  test_templated<int>(); // expected-note{{in instantiation of function template specialization 'test_templated<int>' requested here}}
+  test_templated<int>(); // expected-note 13{{in instantiation of function template specialization 'test_templated<int>' requested here}}
 }
 
 namespace pr35555 {
Index: clang/test/SemaCXX/lambda-unevaluated.cpp
===================================================================
--- clang/test/SemaCXX/lambda-unevaluated.cpp
+++ clang/test/SemaCXX/lambda-unevaluated.cpp
@@ -28,8 +28,10 @@
 
 template <class T>
 auto g(T) -> decltype([]() { T::invalid; } ());
-auto e = g(0); // expected-error{{no matching function for call}}
-// expected-note@-2 {{substitution failure}}
+auto e = g(0); // expected-error@-1{{type 'int' cannot be used prior to '::'}}
+               // expected-note@-1{{while substituting deduced template}}
+               // expected-error@-2 {{no matching function for call to 'g'}}
+               // expected-note@-4 {{substitution failure}}
 
 template <typename T>
 auto foo(decltype([] {
@@ -146,3 +148,34 @@
 namespace lambda_in_trailing_decltype {
 auto x = ([](auto) -> decltype([] {}()) {}(0), 2);
 }
+
+namespace lambda_in_constraints {
+struct WithFoo { static void foo(); };
+
+template <class T>
+concept lambda_works = requires {
+    []() { T::foo(); };
+};
+
+static_assert(!lambda_works<int>);
+static_assert(lambda_works<WithFoo>);
+
+template <class T>
+int* func(T) requires requires { []() { T::foo(); }; };
+double* func(...);
+
+static_assert(__is_same(decltype(func(0)), double*));
+static_assert(__is_same(decltype(func(WithFoo())), int*));
+
+template <class T>
+auto direct_lambda(T) -> decltype([] { T::foo(); }) {}
+void direct_lambda(...) {}
+
+void recursive() {
+    direct_lambda(0); // expected-error@-4 {{type 'int' cannot be used prior to '::'}}
+                      // expected-note@-1 {{while substituting deduced template arguments}}
+    bool x = requires { direct_lambda(0); }; // expected-error@-6 {{type 'int' cannot be used prior to '::'}}
+                                             // expected-note@-1 {{while substituting deduced template arguments}}
+
+}
+}
Index: clang/test/SemaCXX/lambda-expressions.cpp
===================================================================
--- clang/test/SemaCXX/lambda-expressions.cpp
+++ clang/test/SemaCXX/lambda-expressions.cpp
@@ -263,7 +263,7 @@
     // expected-note 4 {{capture 'ts' by}}
   }
   template void nested2(int); // ok
-  template void nested2(int, int); // expected-note {{in instantiation of}}
+  template void nested2(int, int); // expected-note 2 {{in instantiation of}}
 }
 
 namespace PR13860 {
Index: clang/test/SemaCXX/cxx1y-init-captures.cpp
===================================================================
--- clang/test/SemaCXX/cxx1y-init-captures.cpp
+++ clang/test/SemaCXX/cxx1y-init-captures.cpp
@@ -31,7 +31,7 @@
 
   }
 
-  void h(int i, char c) { g(i, c); } //expected-note{{in instantiation}}
+  void h(int i, char c) { g(i, c); } //expected-note 2{{in instantiation}}
 }
 
 namespace odr_use_within_init_capture {
@@ -164,7 +164,7 @@
   return 0;
 }
 
-int run = test(); //expected-note {{instantiation}}
+int run = test(); //expected-note 2 {{instantiation}}
 
 }
 
Index: clang/test/CXX/temp/temp.deduct/p9.cpp
===================================================================
--- /dev/null
+++ clang/test/CXX/temp/temp.deduct/p9.cpp
@@ -0,0 +1,40 @@
+// RUN:  %clang_cc1 -std=c++20 -verify %s
+template <class T>
+auto f(T) -> decltype([]() { T::invalid; } ());
+void f(...);
+void test_f() {
+  f(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
+        // expected-note@-1 {{while substituting deduced template arguments}}
+}
+
+template <class T, unsigned = sizeof([]() { T::invalid; })>
+void g(T);
+void g(...);
+void test_g() {
+  g(0); // expected-error@-4 {{type 'int' cannot be used prior to '::'}}
+        // expected-note@-4 {{in instantiation of default argument}}
+        // expected-note@-2 {{while substituting deduced template arguments}}
+}
+
+template <class T>
+auto h(T) -> decltype([x = T::invalid]() { });
+void h(...);
+void test_h() {
+  h(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
+        // expected-note@-1 {{while substituting deduced template arguments}}
+}
+
+template <class T>
+auto i(T) -> decltype([]() -> typename T::invalid { });
+void i(...);
+void test_i() {
+  i(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
+        // expected-note@-1 {{while substituting deduced template arguments}}
+}
+
+template <class T>
+auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t));   // #1
+void j(...);                                                            // #2
+void test_j() {
+  j(0);
+}
Index: clang/lib/Sema/SemaTemplateInstantiate.cpp
===================================================================
--- clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -34,6 +34,7 @@
 #include "clang/Sema/Template.h"
 #include "clang/Sema/TemplateDeduction.h"
 #include "clang/Sema/TemplateInstCallback.h"
+#include "llvm/ADT/ScopeExit.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 #include <optional>
@@ -367,6 +368,7 @@
   case InitializingStructuredBinding:
   case MarkingClassDllexported:
   case BuildingBuiltinDumpStructCall:
+  case LambdaExpressionSubstitution:
     return false;
 
   // This function should never be called when Kind's value is Memoization.
@@ -959,6 +961,8 @@
       break;
 
     case CodeSynthesisContext::Memoization:
+    case CodeSynthesisContext::LambdaExpressionSubstitution:
+      // FIXME: add a note for lambdas.
       break;
 
     case CodeSynthesisContext::ConstraintsCheck: {
@@ -1016,6 +1020,7 @@
   if (InNonInstantiationSFINAEContext)
     return std::optional<TemplateDeductionInfo *>(nullptr);
 
+  bool SawLambdaSubstitution = false;
   for (SmallVectorImpl<CodeSynthesisContext>::const_reverse_iterator
          Active = CodeSynthesisContexts.rbegin(),
          ActiveEnd = CodeSynthesisContexts.rend();
@@ -1037,6 +1042,15 @@
     case CodeSynthesisContext::NestedRequirementConstraintsCheck:
       // This is a template instantiation, so there is no SFINAE.
       return std::nullopt;
+    case CodeSynthesisContext::LambdaExpressionSubstitution:
+      // [temp.deduct]p9
+      // A lambda-expression appearing in a function type or a template
+      // parameter is not considered part of the immediate context for the
+      // purposes of template argument deduction.
+
+      // We need to check parents.
+      SawLambdaSubstitution = true;
+      break;
 
     case CodeSynthesisContext::DefaultTemplateArgumentInstantiation:
     case CodeSynthesisContext::PriorTemplateArgumentSubstitution:
@@ -1049,12 +1063,17 @@
 
     case CodeSynthesisContext::ExplicitTemplateArgumentSubstitution:
     case CodeSynthesisContext::DeducedTemplateArgumentSubstitution:
+      // We're either substituting explicitly-specified template arguments,
+      // deduced template arguments. SFINAE applies unless we are in a lambda
+      // expression, see [temp.deduct]p9.
+      if (SawLambdaSubstitution)
+        return std::nullopt;
+      [[fallthrough]];
     case CodeSynthesisContext::ConstraintSubstitution:
     case CodeSynthesisContext::RequirementInstantiation:
     case CodeSynthesisContext::RequirementParameterInstantiation:
-      // We're either substituting explicitly-specified template arguments,
-      // deduced template arguments, a constraint expression or a requirement
-      // in a requires expression, so SFINAE applies.
+      // SFINAE always applies in a constraint expression or a requirement
+      // in a requires expression.
       assert(Active->DeductionInfo && "Missing deduction info pointer");
       return Active->DeductionInfo;
 
@@ -1343,6 +1362,14 @@
     ExprResult TransformLambdaExpr(LambdaExpr *E) {
       LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
       Sema::ConstraintEvalRAII<TemplateInstantiator> RAII(*this);
+
+      Sema::CodeSynthesisContext C;
+      C.Kind = clang::Sema::CodeSynthesisContext::LambdaExpressionSubstitution;
+      C.PointOfInstantiation = E->getBeginLoc();
+      SemaRef.pushCodeSynthesisContext(C);
+      auto PopCtx =
+          llvm::make_scope_exit([this] { SemaRef.popCodeSynthesisContext(); });
+
       ExprResult Result = inherited::TransformLambdaExpr(E);
       if (Result.isInvalid())
         return Result;
Index: clang/lib/Frontend/FrontendActions.cpp
===================================================================
--- clang/lib/Frontend/FrontendActions.cpp
+++ clang/lib/Frontend/FrontendActions.cpp
@@ -372,6 +372,8 @@
       return "ExplicitTemplateArgumentSubstitution";
     case CodeSynthesisContext::DeducedTemplateArgumentSubstitution:
       return "DeducedTemplateArgumentSubstitution";
+    case CodeSynthesisContext::LambdaExpressionSubstitution:
+      return "LambdaExpressionSubstitution";
     case CodeSynthesisContext::PriorTemplateArgumentSubstitution:
       return "PriorTemplateArgumentSubstitution";
     case CodeSynthesisContext::DefaultTemplateArgumentChecking:
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -9257,6 +9257,9 @@
       /// a TemplateDecl.
       DeducedTemplateArgumentSubstitution,
 
+      /// We are substituting into a lambda expression.
+      LambdaExpressionSubstitution,
+
       /// We are substituting prior template arguments into a new
       /// template parameter. The template parameter itself is either a
       /// NonTypeTemplateParmDecl or a TemplateTemplateParmDecl.
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -153,6 +153,10 @@
 - A new builtin type trait ``__is_trivially_equaltiy_comparable`` has been added,
   which checks whether comparing two instances of a type is equivalent to
   ``memcmp(&lhs, &rhs, sizeof(T)) == 0``.
+- Clang now implements `[temp.deduct]p9`, subsitution failures inside lambdas from
+  unevaluated contexts will be surfaced as errors. They were previously handled as
+  SFINAE.
+
 
 New Compiler Flags
 ------------------
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to