Skip to content

Conversation

@efriedma-quic
Copy link
Collaborator

@efriedma-quic efriedma-quic commented Jul 8, 2025

127bf44 implemented most of the infrastructure for capturing structured bindings in lambdas, but missed one piece: constant evaluation of such lambdas. Refactor the code to handle this case.

Fixes #145956. Fixes #134897.

127bf44 implemented most of the
infrastructure for capturing structured bindings in lambdas, but missed
one piece: constant evaluation of such lambdas. Refactor the code to
handle this case.

Fixes llvm#145956.
@efriedma-quic efriedma-quic requested review from cor3ntin and shafik July 8, 2025 23:22
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-clang

Author: Eli Friedman (efriedma-quic)

Changes

127bf44 implemented most of the infrastructure for capturing structured bindings in lambdas, but missed one piece: constant evaluation of such lambdas. Refactor the code to handle this case.

Fixes #145956.


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

2 Files Affected:

  • (modified) clang/lib/AST/ExprConstant.cpp (+14-13)
  • (modified) clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp (+28)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 60c658a8d8f99..7d1c196b017b6 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8952,24 +8952,13 @@ static bool EvaluateLValue(const Expr *E, LValue &Result, EvalInfo &Info,
 
 bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) {
   const NamedDecl *D = E->getDecl();
-  if (isa<FunctionDecl, MSGuidDecl, TemplateParamObjectDecl,
-          UnnamedGlobalConstantDecl>(D))
-    return Success(cast<ValueDecl>(D));
-  if (const VarDecl *VD = dyn_cast<VarDecl>(D))
-    return VisitVarDecl(E, VD);
-  if (const BindingDecl *BD = dyn_cast<BindingDecl>(D))
-    return Visit(BD->getBinding());
-  return Error(E);
-}
 
-bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
   // If we are within a lambda's call operator, check whether the 'VD' referred
   // to within 'E' actually represents a lambda-capture that maps to a
   // data-member/field within the closure object, and if so, evaluate to the
   // field or what the field refers to.
   if (Info.CurrentCall && isLambdaCallOperator(Info.CurrentCall->Callee) &&
-      isa<DeclRefExpr>(E) &&
-      cast<DeclRefExpr>(E)->refersToEnclosingVariableOrCapture()) {
+      E->refersToEnclosingVariableOrCapture()) {
     // We don't always have a complete capture-map when checking or inferring if
     // the function call operator meets the requirements of a constexpr function
     // - but we don't need to evaluate the captures to determine constexprness
@@ -8977,13 +8966,25 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
     if (Info.checkingPotentialConstantExpression())
       return false;
 
-    if (auto *FD = Info.CurrentCall->LambdaCaptureFields.lookup(VD)) {
+    if (auto *FD =
+            Info.CurrentCall->LambdaCaptureFields.lookup(cast<ValueDecl>(D))) {
       const auto *MD = cast<CXXMethodDecl>(Info.CurrentCall->Callee);
       return HandleLambdaCapture(Info, E, Result, MD, FD,
                                  FD->getType()->isReferenceType());
     }
   }
 
+  if (isa<FunctionDecl, MSGuidDecl, TemplateParamObjectDecl,
+          UnnamedGlobalConstantDecl>(D))
+    return Success(cast<ValueDecl>(D));
+  if (const VarDecl *VD = dyn_cast<VarDecl>(D))
+    return VisitVarDecl(E, VD);
+  if (const BindingDecl *BD = dyn_cast<BindingDecl>(D))
+    return Visit(BD->getBinding());
+  return Error(E);
+}
+
+bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
   CallStackFrame *Frame = nullptr;
   unsigned Version = 0;
   if (VD->hasLocalStorage()) {
diff --git a/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp
index 0c20dd9dc58c6..33a6039459484 100644
--- a/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp
+++ b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp
@@ -373,3 +373,31 @@ static_assert(
 }
 
 #endif
+
+#ifndef CPP14_AND_EARLIER
+namespace GH145956 {
+  constexpr int f() {
+    struct Pair { int first; int second; };
+    Pair p = {1, 2};
+    auto const& [key, value] = p;
+    return [&] { return key; }();
+#if __cpp_constexpr < 202002L
+    // expected-warning@-2 {{captured structured bindings are a C++20 extension}}
+    // expected-note@-4 {{'key' declared here}}
+#endif
+  }
+  static_assert(f() == 1);
+  constexpr auto retlambda() {
+    struct Pair { int first; int second; };
+    Pair p = {1, 2};
+    auto const& [key, value] = p;
+    return [=] { return key; };
+#if __cpp_constexpr < 202002L
+    // expected-warning@-2 {{captured structured bindings are a C++20 extension}}
+    // expected-note@-4 {{'key' declared here}}
+#endif
+  }
+  constexpr auto lambda = retlambda();
+  static_assert(lambda() == 1);
+}
+#endif

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

}

bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) {
const NamedDecl *D = E->getDecl();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const NamedDecl *D = E->getDecl();
const ValueDecl *D = E->getDecl();

That way we can avoid casts further down

@efriedma-quic efriedma-quic merged commit aa27d4e into llvm:main Jul 9, 2025
10 checks passed
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

3 participants