From f0447fa1442b0d033b83186f6dcaa89ffe57c281 Mon Sep 17 00:00:00 2001 From: Eli Friedman Date: Tue, 8 Jul 2025 16:14:06 -0700 Subject: [PATCH 1/3] [clang] Implemeent consteval for captured structured bindings. 127bf44385424891eb04cff8e52d3f157fc2cb7c 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. --- clang/lib/AST/ExprConstant.cpp | 27 +++++++++--------- .../test/SemaCXX/cxx1z-constexpr-lambdas.cpp | 28 +++++++++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) 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(D)) - return Success(cast(D)); - if (const VarDecl *VD = dyn_cast(D)) - return VisitVarDecl(E, VD); - if (const BindingDecl *BD = dyn_cast(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(E) && - cast(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(D))) { const auto *MD = cast(Info.CurrentCall->Callee); return HandleLambdaCapture(Info, E, Result, MD, FD, FD->getType()->isReferenceType()); } } + if (isa(D)) + return Success(cast(D)); + if (const VarDecl *VD = dyn_cast(D)) + return VisitVarDecl(E, VD); + if (const BindingDecl *BD = dyn_cast(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 From 3b7bcdffe57d3104b402dc8a2f2a44b8d6efee08 Mon Sep 17 00:00:00 2001 From: Eli Friedman Date: Tue, 8 Jul 2025 16:26:21 -0700 Subject: [PATCH 2/3] Add release note. --- clang/docs/ReleaseNotes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 8b4f9229c4463..24f296b0f7ce0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -145,6 +145,8 @@ C++20 Feature Support - Fixed a crash with a defaulted spaceship (``<=>``) operator when the class contains a member declaration of vector type. Vector types cannot yet be compared directly, so this causes the operator to be deleted. (#GH137452) +- Implement constant evaluation of lambdas that capture structured bindings. + (#GH145956) C++17 Feature Support ^^^^^^^^^^^^^^^^^^^^^ From aed9038f12e3fbb0b7eee1b89c2a60b2025538cd Mon Sep 17 00:00:00 2001 From: Eli Friedman Date: Wed, 9 Jul 2025 00:28:59 -0700 Subject: [PATCH 3/3] Fix casting. --- clang/lib/AST/ExprConstant.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7d1c196b017b6..fc6e8843b5ca7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8951,7 +8951,7 @@ static bool EvaluateLValue(const Expr *E, LValue &Result, EvalInfo &Info, } bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { - const NamedDecl *D = E->getDecl(); + const ValueDecl *D = E->getDecl(); // 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 @@ -8966,8 +8966,7 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { if (Info.checkingPotentialConstantExpression()) return false; - if (auto *FD = - Info.CurrentCall->LambdaCaptureFields.lookup(cast(D))) { + if (auto *FD = Info.CurrentCall->LambdaCaptureFields.lookup(D)) { const auto *MD = cast(Info.CurrentCall->Callee); return HandleLambdaCapture(Info, E, Result, MD, FD, FD->getType()->isReferenceType());