cor3ntin created this revision.
Herald added a project: All.
cor3ntin requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

A lambda call operator can be a templated entity -
and therefore have constraints while not being a function template

  template<class T> void f() {
    []() requires false { }();
  }

In that case, we would check the constraints of the call operator
which is non-viable. However, we would find a viable candidate:
the conversion operator to function pointer, and use it to
perform a surrogate call.
These constraints were not checked because:

- We never check the constraints of surrogate functions
- The lambda conversion operator has non constraints.

>From the wording, it is not clear what the intent is but
it seems reasonable to expect the constraints of the lambda conversion
operator to be checked and it is consistent with GCC and MSVC.

This patch also improve the diagnostics for constraint failure
on surrogate calls.

Fixes #63181


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D154368

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/Sema/SemaLambda.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/SemaTemplate/concepts.cpp

Index: clang/test/SemaTemplate/concepts.cpp
===================================================================
--- clang/test/SemaTemplate/concepts.cpp
+++ clang/test/SemaTemplate/concepts.cpp
@@ -410,8 +410,8 @@
 
 template <typename U>
 void SingleDepthReferencesTopLambda(U &&u) {
-  []()
-    requires IsInt<decltype(u)>
+  []() // #SDRTL_OP
+    requires IsInt<decltype(u)> // #SDRTL_REQ
   {}();
 }
 
@@ -434,8 +434,8 @@
 
 template <typename U>
 void DoubleDepthReferencesTopLambda(U &&u) {
-  []() { []()
-           requires IsInt<decltype(u)>
+  []() { []() // #DDRTL_OP
+           requires IsInt<decltype(u)> // #DDRTL_REQ
          {}(); }();
 }
 
@@ -459,10 +459,11 @@
 
 template <typename U>
 void DoubleDepthReferencesAllLambda(U &&u) {
-  [](U &&u2) {
-    [](U && u3)
-      requires IsInt<decltype(u)> &&
-               IsInt<decltype(u2)> && IsInt<decltype(u3)>
+  [](U &&u2) { // #DDRAL_OP1
+    [](U && u3) // #DDRAL_OP2
+      requires IsInt<decltype(u)> // #DDRAL_REQ
+            && IsInt<decltype(u2)>
+            && IsInt<decltype(u3)>
     {}(u2);
   }(u);
 }
@@ -484,8 +485,8 @@
 template <typename T>
 void ChecksLocalVar(T x) {
   T Local;
-  []()
-    requires(IsInt<decltype(Local)>)
+  []() // #CLV_OP
+    requires(IsInt<decltype(Local)>) // #CLV_REQ
   {}();
 }
 
@@ -529,6 +530,11 @@
   SingleDepthReferencesTopLambda(v);
   // FIXME: This should error on constraint failure! (Lambda!)
   SingleDepthReferencesTopLambda(will_fail);
+  // expected-note@-1{{in instantiation of function template specialization}}
+  // expected-error@#SDRTL_OP{{no matching function for call to object of type}}
+  // expected-note@#SDRTL_OP{{candidate function not viable: constraints not satisfied}}
+  // expected-note@#SDRTL_REQ{{because 'IsInt<decltype(u)>' evaluated to false}}
+
   DoubleDepthReferencesTop(v);
   DoubleDepthReferencesTop(will_fail);
   // expected-error@#DDRT_CALL{{no matching function for call to object of type 'lc2'}}
@@ -538,8 +544,12 @@
   // expected-note@#DDRT_REQ{{'IsInt<decltype(u)>' evaluated to false}}
 
   DoubleDepthReferencesTopLambda(v);
-  // FIXME: This should error on constraint failure! (Lambda!)
   DoubleDepthReferencesTopLambda(will_fail);
+  // expected-note@-1{{in instantiation of function template specialization}}
+  // expected-error@#DDRTL_OP{{no matching function for call to object of type}}
+  // expected-note@#DDRTL_OP{{candidate function not viable: constraints not satisfied}}
+  // expected-note@#DDRTL_OP{{while substituting into a lambda expression here}}
+  // expected-note@#DDRTL_REQ{{because 'IsInt<decltype(u)>' evaluated to false}}
   DoubleDepthReferencesAll(v);
   DoubleDepthReferencesAll(will_fail);
   // expected-error@#DDRA_CALL{{no matching function for call to object of type 'lc2'}}
@@ -549,8 +559,12 @@
   // expected-note@#DDRA_REQ{{'IsInt<decltype(u)>' evaluated to false}}
 
   DoubleDepthReferencesAllLambda(v);
-  // FIXME: This should error on constraint failure! (Lambda!)
   DoubleDepthReferencesAllLambda(will_fail);
+  // expected-note@-1{{in instantiation of function template specialization}}
+  // expected-note@#DDRAL_OP1{{while substituting into a lambda expression here}}
+  // expected-error@#DDRAL_OP2{{no matching function for call to object of type}}
+  // expected-note@#DDRAL_OP2{{candidate function not viable: constraints not satisfied}}
+  // expected-note@#DDRAL_REQ{{because 'IsInt<decltype(u)>' evaluated to false}}
 
   CausesFriendConstraint<int> CFC;
   FriendFunc(CFC, 1);
@@ -565,6 +579,12 @@
   ChecksLocalVar(v);
   // FIXME: This should error on constraint failure! (Lambda!)
   ChecksLocalVar(will_fail);
+  // expected-note@-1{{in instantiation of function template specialization}}
+  // expected-error@#CLV_OP{{no matching function for call to object of type}}
+  // expected-note@#CLV_OP{{candidate function not viable: constraints not satisfied}}
+  // expected-note@#CLV_REQ{{because 'IsInt<decltype(Local)>' evaluated to false}}
+
+
 
   LocalStructMemberVar(v);
   LocalStructMemberVar(will_fail);
@@ -701,6 +721,18 @@
 } // namespace SelfFriend
 
 
+namespace Surrogates {
+int f1(int);
+template <auto N>
+struct A {
+    using F = int(int);
+    operator F*() requires N { return f1; } // expected-note{{candidate surrogate function 'operator int (*)(int)' not viable: constraints not satisfied}}
+};
+int i = A<true>{}(0);
+int j = A<false>{}(0); // expected-error{{no matching function for call to object of type 'A<false>'}}
+}
+
+
 namespace ConstrainedMemberVarTemplate {
 template <long Size> struct Container {
   static constexpr long arity = Size;
@@ -914,3 +946,34 @@
 
 static_assert(W0<0>::W1<1>::F<int>::value == 1);
 } // TemplateInsideTemplateInsideTemplate
+
+
+namespace GH63181 {
+
+template<auto N, class T> void f() {
+auto l = []() requires N { }; // expected-note 2{{candidate function not viable: constraints not satisfied}} \
+                              // expected-note 2{{because 'false' evaluated to false}}
+
+l();
+// expected-error@-1 {{no matching function for call to object of type}}
+void(*ptr)() = l;
+// expected-error-re@-1 {{no viable conversion from '(lambda {{.*}})' to 'void (*)()'}}
+}
+
+template void f<false, int>(); // expected-note {{in instantiation of function template specialization 'GH63181::f<false, int>' requested here}}
+template void f<true, int>();
+
+template<class T> concept C = __is_same(T, int); // expected-note{{because '__is_same(char, int)' evaluated to false}}
+
+template<class... Ts> void f() {
+  ([]() requires C<Ts> { return Ts(); }(), ...);
+  // expected-error@-1 {{no matching function for call to object of type}} \
+  // expected-note@-1 {{candidate function not viable: constraints not satisfied}} \
+  // expected-note@-1 {{because 'char' does not satisfy 'C'}}
+}
+
+template void f<int, int, int>();
+template void f<int, int, char>();
+//expected-note@-1{{in instantiation of function template specialization 'GH63181::f<int, int, char>' requested here}}
+
+}
Index: clang/lib/Sema/SemaOverload.cpp
===================================================================
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTLambda.h"
 #include "clang/AST/CXXInheritance.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
@@ -7578,8 +7579,15 @@
   }
 
   if (Conversion->getTrailingRequiresClause()) {
+    CXXMethodDecl *M = Conversion;
+    // A lambda conversion operator has the same constraints
+    // as the call operator, and constraints checking
+    // relies on whether we are in a lambda call operator
+    // so use that instead.
+    if (isLambdaConversionOperator(Conversion))
+      M = Conversion->getParent()->getLambdaCallOperator();
     ConstraintSatisfaction Satisfaction;
-    if (CheckFunctionConstraints(Conversion, Satisfaction) ||
+    if (CheckFunctionConstraints(M, Satisfaction) ||
         !Satisfaction.IsSatisfied) {
       Candidate.Viable = false;
       Candidate.FailureKind = ovl_fail_constraints_not_satisfied;
@@ -7851,6 +7859,25 @@
     }
   }
 
+  if (Conversion->getTrailingRequiresClause()) {
+    CXXMethodDecl *M = Conversion;
+    // A lambda conversion operator has the same constraints
+    // as the call operator, and constraints checking
+    // relies on whether we are in a lambda call operator
+    // so use that instead.
+    if (isLambdaConversionOperator(Conversion))
+      M = Conversion->getParent()->getLambdaCallOperator();
+
+    ConstraintSatisfaction Satisfaction;
+    if (CheckFunctionConstraints(M, Satisfaction, /*Loc*/ {},
+                                 /*ForOverloadResolution*/ true) ||
+        !Satisfaction.IsSatisfied) {
+      Candidate.Viable = false;
+      Candidate.FailureKind = ovl_fail_constraints_not_satisfied;
+      return;
+    }
+  }
+
   if (EnableIfAttr *FailedAttr =
           CheckEnableIf(Conversion, CandidateSet.getLocation(), std::nullopt)) {
     Candidate.Viable = false;
@@ -11620,8 +11647,17 @@
   if (isRValueReference) FnType = S.Context.getRValueReferenceType(FnType);
   if (isLValueReference) FnType = S.Context.getLValueReferenceType(FnType);
 
-  S.Diag(Cand->Surrogate->getLocation(), diag::note_ovl_surrogate_cand)
-    << FnType;
+  if (Cand->FailureKind == ovl_fail_constraints_not_satisfied) {
+    S.Diag(Cand->Surrogate->getLocation(),
+           diag::note_ovl_surrogate_constraints_not_satisfied)
+        << Cand->Surrogate;
+    ConstraintSatisfaction Satisfaction;
+    if (S.CheckFunctionConstraints(Cand->Surrogate, Satisfaction))
+      S.DiagnoseUnsatisfiedConstraint(Satisfaction);
+  } else {
+    S.Diag(Cand->Surrogate->getLocation(), diag::note_ovl_surrogate_cand)
+        << FnType;
+  }
 }
 
 static void NoteBuiltinOperatorCandidate(Sema &S, StringRef Opc,
@@ -14944,6 +14980,22 @@
                        /*SuppressUserConversion=*/false);
   }
 
+  // When calling a lambda, both the call operator, and
+  // the conversion operator to function pointer
+  // are considered. But when constraint checking
+  // on the call operator fails, it will also fail on the
+  // conversion operator as the constraints are always the same.
+  // As the user probably do not intend to perform a surrogate call,
+  // we filter them out to produce better error diagnostics, ie to avoid
+  // showing 2 failed overloads instead of one.
+  bool IgnoreSurrogateFunctions = false;
+  if (CandidateSet.size() == 1 && Record->getAsCXXRecordDecl()->isLambda()) {
+    const OverloadCandidate &Candidate = *CandidateSet.begin();
+    if (!Candidate.Viable &&
+        Candidate.FailureKind == ovl_fail_constraints_not_satisfied)
+      IgnoreSurrogateFunctions = true;
+  }
+
   // C++ [over.call.object]p2:
   //   In addition, for each (non-explicit in C++0x) conversion function
   //   declared in T of the form
@@ -14963,7 +15015,8 @@
   //   within T by another intervening declaration.
   const auto &Conversions =
       cast<CXXRecordDecl>(Record->getDecl())->getVisibleConversionFunctions();
-  for (auto I = Conversions.begin(), E = Conversions.end(); I != E; ++I) {
+  for (auto I = Conversions.begin(), E = Conversions.end();
+       !IgnoreSurrogateFunctions && I != E; ++I) {
     NamedDecl *D = *I;
     CXXRecordDecl *ActingContext = cast<CXXRecordDecl>(D->getDeclContext());
     if (isa<UsingShadowDecl>(D))
Index: clang/lib/Sema/SemaLambda.cpp
===================================================================
--- clang/lib/Sema/SemaLambda.cpp
+++ clang/lib/Sema/SemaLambda.cpp
@@ -1633,6 +1633,14 @@
   Conversion->setAccess(AS_public);
   Conversion->setImplicit(true);
 
+  // A non-generic lambda may still be a templated entity
+  // We need to preserve constraints when
+  // taking the address of the call operator as well as performing
+  // surrogate calls.
+  // See GH63181.
+  if (Expr *Requires = CallOperator->getTrailingRequiresClause())
+    Conversion->setTrailingRequiresClause(Requires);
+
   if (Class->isGenericLambda()) {
     // Create a template version of the conversion operator, using the template
     // parameter list of the function call operator.
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4714,6 +4714,9 @@
 def note_ovl_candidate_constraints_not_satisfied : Note<
     "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: constraints "
     "not satisfied">;
+def note_ovl_surrogate_constraints_not_satisfied : Note<
+    "candidate surrogate function %0 not viable: constraints "
+    "not satisfied">;
 def note_implicit_member_target_infer_collision : Note<
     "implicit %sub{select_special_member_kind}0 inferred target collision: call to both "
     "%select{__device__|__global__|__host__|__host__ __device__}1 and "
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -630,6 +630,8 @@
 - Allow abstract parameter and return types in functions that are
   either deleted or not defined.
   (`#63012 <https://github.com/llvm/llvm-project/issues/63012>`_)
+- Fix constraint checking of non-generic lambdas.
+  (`#63181 <https://github.com/llvm/llvm-project/issues/63181>`_)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to