[clang] f9caa12 - [Clang] Fix constraint checking of non-generic lambdas.

Corentin Jabot via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 21 01:59:41 PDT 2023


Author: Corentin Jabot
Date: 2023-07-21T10:59:36+02:00
New Revision: f9caa12328b265b77221fe7a310d4504673d814a

URL: https://github.com/llvm/llvm-project/commit/f9caa12328b265b77221fe7a310d4504673d814a
DIFF: https://github.com/llvm/llvm-project/commit/f9caa12328b265b77221fe7a310d4504673d814a.diff

LOG: [Clang] Fix constraint checking of non-generic lambdas.

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

Reviewed By: #clang-language-wg, aaron.ballman

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

Added: 
    

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

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 05a7ec04d60c31..45381e7aad4ffc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -777,6 +777,8 @@ Bug Fixes to C++ Support
 - Fix location of default member initialization in parenthesized aggregate
   initialization.
   (`#63903 <https://github.com/llvm/llvm-project/issues/63903>`_)
+- Fix constraint checking of non-generic lambdas.
+  (`#63181 <https://github.com/llvm/llvm-project/issues/63181>`_)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 569c6a054e7102..99917f190d82a1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4732,6 +4732,8 @@ def note_ovl_candidate_bad_target : Note<
 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<
+    "conversion candidate %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 "

diff  --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index f562e85009282a..f24b549dd2ef7a 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -679,6 +679,15 @@ bool Sema::CheckFunctionConstraints(const FunctionDecl *FD,
     return false;
   }
 
+  // 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
+  // (and may refer to its parameters), so check the call operator instead.
+  if (const auto *MD = dyn_cast<CXXConversionDecl>(FD);
+      MD && isLambdaConversionOperator(const_cast<CXXConversionDecl *>(MD)))
+    return CheckFunctionConstraints(MD->getParent()->getLambdaCallOperator(),
+                                    Satisfaction, UsageLoc,
+                                    ForOverloadResolution);
+
   DeclContext *CtxToSave = const_cast<FunctionDecl *>(FD);
 
   while (isLambdaCallOperator(CtxToSave) || FD->isTransparentContext()) {

diff  --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 339d16b080ee22..06fc53591a7643 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1633,6 +1633,11 @@ static void addFunctionPointerConversion(Sema &S, SourceRange IntroducerRange,
   Conversion->setAccess(AS_public);
   Conversion->setImplicit(true);
 
+  // A non-generic lambda may still be a templated entity. We need to preserve
+  // constraints when converting the lambda to a function pointer. 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.

diff  --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c70d37de0a0130..42a367dca531d5 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/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"
@@ -7885,6 +7886,17 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
     }
   }
 
+  if (Conversion->getTrailingRequiresClause()) {
+    ConstraintSatisfaction Satisfaction;
+    if (CheckFunctionConstraints(Conversion, 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;
@@ -11646,8 +11658,17 @@ static void NoteSurrogateCandidate(Sema &S, OverloadCandidate *Cand) {
   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,
@@ -14970,6 +14991,22 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
                        /*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 does 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
@@ -14989,7 +15026,8 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
   //   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))

diff  --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index c6fb5c441abd6b..9776ddbb372991 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -410,8 +410,8 @@ void SingleDepthReferencesTopCalled(U &&u) {
 
 template <typename U>
 void SingleDepthReferencesTopLambda(U &&u) {
-  []()
-    requires IsInt<decltype(u)>
+  []() // #SDRTL_OP
+    requires IsInt<decltype(u)> // #SDRTL_REQ
   {}();
 }
 
@@ -434,8 +434,8 @@ void DoubleDepthReferencesTop(U &&u) {
 
 template <typename U>
 void DoubleDepthReferencesTopLambda(U &&u) {
-  []() { []()
-           requires IsInt<decltype(u)>
+  []() { []() // #DDRTL_OP
+           requires IsInt<decltype(u)> // #DDRTL_REQ
          {}(); }();
 }
 
@@ -459,10 +459,11 @@ void DoubleDepthReferencesAll(U &&u) {
 
 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 @@ struct CausesFriendConstraint {
 template <typename T>
 void ChecksLocalVar(T x) {
   T Local;
-  []()
-    requires(IsInt<decltype(Local)>)
+  []() // #CLV_OP
+    requires(IsInt<decltype(Local)>) // #CLV_REQ
   {}();
 }
 
@@ -527,8 +528,12 @@ void test_dependent() {
   SingleDepthReferencesTopNotCalled(will_fail);
   SingleDepthReferencesTopCalled(v); // #SDRTC
   SingleDepthReferencesTopLambda(v);
-  // FIXME: This should error on constraint failure! (Lambda!)
   SingleDepthReferencesTopLambda(will_fail);
+  // expected-note at -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 +543,12 @@ void test_dependent() {
   // 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 at -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 +558,12 @@ void test_dependent() {
   // 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 at -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);
@@ -563,8 +576,13 @@ void test_dependent() {
   // ChecksCapture(v);
 
   ChecksLocalVar(v);
-  // FIXME: This should error on constraint failure! (Lambda!)
   ChecksLocalVar(will_fail);
+  // expected-note at -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 +719,18 @@ namespace SelfFriend {
 } // namespace SelfFriend
 
 
+namespace Surrogates {
+int f1(int);
+template <auto N>
+struct A {
+    using F = int(int);
+    operator F*() requires N { return f1; } // expected-note{{conversion candidate '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 +944,53 @@ struct W0 {
 
 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 at -1 {{no matching function for call to object of type}}
+void(*ptr)() = l;
+// expected-error-re at -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 at -1 {{no matching function for call to object of type}} \
+  // expected-note at -1 {{candidate function not viable: constraints not satisfied}} \
+  // expected-note at -1 {{because 'char' does not satisfy 'C'}}
+}
+
+template void f<int, int, int>();
+template void f<int, int, char>();
+//expected-note at -1{{in instantiation of function template specialization 'GH63181::f<int, int, char>' requested here}}
+
+
+template <typename T, bool IsTrue>
+concept Test = IsTrue; // expected-note 2{{because 'false' evaluated to false}}
+
+template <typename T, bool IsTrue>
+void params() {
+    auto l = [](T t)  // expected-note 2{{candidate function not viable: constraints not satisfied}}
+    requires Test<decltype(t), IsTrue> // expected-note 2{{because 'Test<decltype(t), false>' evaluated to false}}
+    {};
+    using F = void(T);
+    F* f = l; // expected-error {{no viable conversion from}}
+    l(0); // expected-error {{no matching function for call to object}}
+}
+
+void test_params() {
+    params<int, true>();
+    params<int, false>(); // expected-note {{in instantiation of function template specialization 'GH63181::params<int, false>' requested here}}
+}
+
+}


        


More information about the cfe-commits mailing list