[clang] [Clang] Implement P0963R3 "Structured binding declaration as a condition" (PR #130228)

Younan Zhang via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 9 22:57:24 PDT 2025


https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/130228

>From 295b8173b6913d9014c5786eb4af0112384afa65 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 7 Mar 2025 11:38:11 +0800
Subject: [PATCH 1/4] [Clang] Implement P0963R3 "Structured binding declaration
 as a condition"

---
 clang/include/clang/AST/DeclCXX.h             | 23 +++++++++++-------
 .../clang/Basic/DiagnosticSemaKinds.td        |  8 +++++--
 clang/lib/AST/ASTImporter.cpp                 |  7 +++---
 clang/lib/AST/DeclCXX.cpp                     | 21 +++++++++-------
 clang/lib/AST/ExprConstant.cpp                | 24 +++++++++++++++----
 clang/lib/CodeGen/CGDecl.cpp                  | 13 ++++++----
 clang/lib/CodeGen/CGStmt.cpp                  | 17 ++++++++++---
 clang/lib/CodeGen/CodeGenFunction.cpp         |  7 +++++-
 clang/lib/CodeGen/CodeGenFunction.h           |  5 +++-
 clang/lib/Sema/SemaDecl.cpp                   |  6 ++---
 clang/lib/Sema/SemaDeclCXX.cpp                | 17 +++++++------
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  8 ++++---
 clang/lib/Serialization/ASTReaderDecl.cpp     |  9 ++++---
 clang/lib/Serialization/ASTWriterDecl.cpp     |  1 +
 clang/test/AST/ByteCode/if.cpp                |  2 +-
 clang/test/Parser/cxx1z-decomposition.cpp     | 12 +++++-----
 clang/test/Parser/decomposed-condition.cpp    | 24 +++++++++----------
 clang/test/SemaCXX/decomposed-condition.cpp   |  4 ++--
 18 files changed, 136 insertions(+), 72 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index dbd02ef7f8011..414a5aea32566 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -4224,15 +4224,18 @@ class DecompositionDecl final
     : public VarDecl,
       private llvm::TrailingObjects<DecompositionDecl, BindingDecl *> {
   /// The number of BindingDecl*s following this object.
-  unsigned NumBindings;
+  unsigned NumBindings : 31;
+
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned IsDecisionVariable : 1;
 
   DecompositionDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
                     SourceLocation LSquareLoc, QualType T,
                     TypeSourceInfo *TInfo, StorageClass SC,
-                    ArrayRef<BindingDecl *> Bindings)
+                    ArrayRef<BindingDecl *> Bindings, bool IsDecisionVariable)
       : VarDecl(Decomposition, C, DC, StartLoc, LSquareLoc, nullptr, T, TInfo,
                 SC),
-        NumBindings(Bindings.size()) {
+        NumBindings(Bindings.size()), IsDecisionVariable(IsDecisionVariable) {
     std::uninitialized_copy(Bindings.begin(), Bindings.end(),
                             getTrailingObjects<BindingDecl *>());
     for (auto *B : Bindings) {
@@ -4253,12 +4256,14 @@ class DecompositionDecl final
 
   static DecompositionDecl *Create(ASTContext &C, DeclContext *DC,
                                    SourceLocation StartLoc,
-                                   SourceLocation LSquareLoc,
-                                   QualType T, TypeSourceInfo *TInfo,
-                                   StorageClass S,
-                                   ArrayRef<BindingDecl *> Bindings);
+                                   SourceLocation LSquareLoc, QualType T,
+                                   TypeSourceInfo *TInfo, StorageClass S,
+                                   ArrayRef<BindingDecl *> Bindings,
+                                   bool IsDecisionVariable);
+
   static DecompositionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
-                                               unsigned NumBindings);
+                                               unsigned NumBindings,
+                                               bool IsDecisionVariable);
 
   // Provide the range of bindings which may have a nested pack.
   llvm::ArrayRef<BindingDecl *> bindings() const {
@@ -4285,6 +4290,8 @@ class DecompositionDecl final
                                             std::move(Bindings));
   }
 
+  bool isDecisionVariable() const { return IsDecisionVariable; }
+
   void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override;
 
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 0b121c04cd3c0..fbc91f2eb8dd6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -529,8 +529,12 @@ def warn_cxx14_compat_decomp_decl : Warning<
 def ext_decomp_decl : ExtWarn<
   "decomposition declarations are a C++17 extension">, InGroup<CXX17>;
 def ext_decomp_decl_cond : ExtWarn<
-  "ISO C++17 does not permit structured binding declaration in a condition">,
-  InGroup<DiagGroup<"binding-in-condition">>;
+  "structured binding declaration in a condition is a C++2c extenstion">,
+  InGroup<CXX26>;
+def warn_cxx26_decomp_decl_cond : Warning<
+  "structured binding declaration in a condition is incompatible with "
+  "C++ standards before C++2c">,
+  InGroup<CXXPre26Compat>, DefaultIgnore;
 def err_decomp_decl_spec : Error<
   "decomposition declaration cannot be declared "
   "%plural{1:'%1'|:with '%1' specifiers}0">;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 82180486f3c7c..95ca085c5b746 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4614,9 +4614,10 @@ ExpectedDecl ASTNodeImporter::VisitVarDecl(VarDecl *D) {
             ImportArrayChecked(FromDecomp->bindings(), Bindings.begin()))
       return std::move(Err);
     DecompositionDecl *ToDecomp;
-    if (GetImportedOrCreateDecl(
-            ToDecomp, FromDecomp, Importer.getToContext(), DC, ToInnerLocStart,
-            Loc, ToType, ToTypeSourceInfo, D->getStorageClass(), Bindings))
+    if (GetImportedOrCreateDecl(ToDecomp, FromDecomp, Importer.getToContext(),
+                                DC, ToInnerLocStart, Loc, ToType,
+                                ToTypeSourceInfo, D->getStorageClass(),
+                                Bindings, FromDecomp->isDecisionVariable()))
       return ToDecomp;
     ToVar = ToDecomp;
   } else {
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 7eff776882629..fdad1150c8b1a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3520,21 +3520,24 @@ DecompositionDecl *DecompositionDecl::Create(ASTContext &C, DeclContext *DC,
                                              SourceLocation LSquareLoc,
                                              QualType T, TypeSourceInfo *TInfo,
                                              StorageClass SC,
-                                             ArrayRef<BindingDecl *> Bindings) {
+                                             ArrayRef<BindingDecl *> Bindings,
+                                             bool IsDecisionVariable) {
   size_t Extra = additionalSizeToAlloc<BindingDecl *>(Bindings.size());
-  return new (C, DC, Extra)
-      DecompositionDecl(C, DC, StartLoc, LSquareLoc, T, TInfo, SC, Bindings);
+  return new (C, DC, Extra) DecompositionDecl(
+      C, DC, StartLoc, LSquareLoc, T, TInfo, SC, Bindings, IsDecisionVariable);
 }
 
-DecompositionDecl *DecompositionDecl::CreateDeserialized(ASTContext &C,
-                                                         GlobalDeclID ID,
-                                                         unsigned NumBindings) {
+DecompositionDecl *
+DecompositionDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
+                                      unsigned NumBindings,
+                                      bool IsDecisionVariable) {
   size_t Extra = additionalSizeToAlloc<BindingDecl *>(NumBindings);
-  auto *Result = new (C, ID, Extra)
-      DecompositionDecl(C, nullptr, SourceLocation(), SourceLocation(),
-                        QualType(), nullptr, StorageClass(), {});
+  auto *Result = new (C, ID, Extra) DecompositionDecl(
+      C, nullptr, SourceLocation(), SourceLocation(), QualType(), nullptr,
+      StorageClass(), {}, IsDecisionVariable);
   // Set up and clean out the bindings array.
   Result->NumBindings = NumBindings;
+  Result->IsDecisionVariable = IsDecisionVariable;
   auto *Trail = Result->getTrailingObjects<BindingDecl *>();
   for (unsigned I = 0; I != NumBindings; ++I)
     new (Trail + I) BindingDecl*(nullptr);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 7244120d1be51..564a47a839336 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5218,16 +5218,28 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
   return true;
 }
 
+static bool EvaluateDecompositionDeclInit(EvalInfo &Info,
+                                          const DecompositionDecl *DD);
+
 static bool EvaluateDecl(EvalInfo &Info, const Decl *D) {
   bool OK = true;
 
   if (const VarDecl *VD = dyn_cast<VarDecl>(D))
     OK &= EvaluateVarDecl(Info, VD);
 
-  if (const DecompositionDecl *DD = dyn_cast<DecompositionDecl>(D))
-    for (auto *BD : DD->flat_bindings())
-      if (auto *VD = BD->getHoldingVar())
-        OK &= EvaluateDecl(Info, VD);
+  if (const DecompositionDecl *DD = dyn_cast<DecompositionDecl>(D);
+      DD && !DD->isDecisionVariable())
+    OK &= EvaluateDecompositionDeclInit(Info, DD);
+
+  return OK;
+}
+
+static bool EvaluateDecompositionDeclInit(EvalInfo &Info,
+                                          const DecompositionDecl *DD) {
+  bool OK = true;
+  for (auto *BD : DD->flat_bindings())
+    if (auto *VD = BD->getHoldingVar())
+      OK &= EvaluateDecl(Info, VD);
 
   return OK;
 }
@@ -5251,6 +5263,10 @@ static bool EvaluateCond(EvalInfo &Info, const VarDecl *CondDecl,
     return false;
   if (!EvaluateAsBooleanCondition(Cond, Result, Info))
     return false;
+  if (auto *DD = dyn_cast_if_present<DecompositionDecl>(CondDecl);
+      DD && DD->isDecisionVariable() &&
+      !EvaluateDecompositionDeclInit(Info, DD))
+    return false;
   return Scope.destroy();
 }
 
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 9cd5885aaae51..44d787a85f691 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -164,10 +164,9 @@ void CodeGenFunction::EmitDecl(const Decl &D) {
     assert(VD.isLocalVarDecl() &&
            "Should not see file-scope variables inside a function!");
     EmitVarDecl(VD);
-    if (auto *DD = dyn_cast<DecompositionDecl>(&VD))
-      for (auto *B : DD->flat_bindings())
-        if (auto *HD = B->getHoldingVar())
-          EmitVarDecl(*HD);
+    if (auto *DD = dyn_cast<DecompositionDecl>(&VD);
+        DD && !DD->isDecisionVariable())
+      EmitDecompositionVarInit(*DD);
 
     return;
   }
@@ -2057,6 +2056,12 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
                         /*IsAutoInit=*/false);
 }
 
+void CodeGenFunction::EmitDecompositionVarInit(const DecompositionDecl &DD) {
+  for (auto *B : DD.flat_bindings())
+    if (auto *HD = B->getHoldingVar())
+      EmitVarDecl(*HD);
+}
+
 /// Emit an expression as an initializer for an object (variable, field, etc.)
 /// at the given location.  The expression is not necessarily the normal
 /// initializer for the object, and the address is not necessarily
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index abe799af32c6e..1d71f22dc2d47 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -913,6 +913,11 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {
       if (CondConstant)
         incrementProfileCounter(&S);
       if (Executed) {
+        if (auto *DD = dyn_cast_if_present<DecompositionDecl>(
+                S.getConditionVariable())) {
+          assert(DD->isDecisionVariable());
+          EmitDecompositionVarInit(*DD);
+        }
         RunCleanupsScope ExecutedScope(*this);
         EmitStmt(Executed);
       }
@@ -952,10 +957,16 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {
   // there is a 'return' within the body, but this is particularly beneficial
   // when one if-stmt is nested within another if-stmt so that all of the MC/DC
   // updates are kept linear and consistent.
-  if (!CGM.getCodeGenOpts().MCDCCoverage)
-    EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH);
-  else {
+  if (!CGM.getCodeGenOpts().MCDCCoverage) {
+    EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH,
+                         nullptr, S.getConditionVariable());
+  } else {
     llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond());
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable())) {
+      assert(DD->isDecisionVariable());
+      EmitDecompositionVarInit(*DD);
+    }
     Builder.CreateCondBr(BoolCondVal, ThenBlock, ElseBlock);
   }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 08165e0b28406..c17a7d308bca8 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1846,7 +1846,8 @@ void CodeGenFunction::EmitBranchToCounterBlock(
 /// LHS and RHS nodes.
 void CodeGenFunction::EmitBranchOnBoolExpr(
     const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock,
-    uint64_t TrueCount, Stmt::Likelihood LH, const Expr *ConditionalOp) {
+    uint64_t TrueCount, Stmt::Likelihood LH, const Expr *ConditionalOp,
+    const VarDecl *ConditionalDecl) {
   Cond = Cond->IgnoreParens();
 
   if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(Cond)) {
@@ -2047,6 +2048,10 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
     CondV = EvaluateExprAsBool(Cond);
   }
 
+  if (auto *DD = dyn_cast_if_present<DecompositionDecl>(ConditionalDecl);
+      DD && DD->isDecisionVariable())
+    EmitDecompositionVarInit(*DD);
+
   // If not at the top of the logical operator nest, update MCDC temp with the
   // boolean result of the evaluated condition.
   if (!MCDCLogOpStack.empty()) {
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 018fc66b72a1e..3586594429da8 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3474,6 +3474,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void emitAutoVarTypeCleanup(const AutoVarEmission &emission,
                               QualType::DestructionKind dtorKind);
 
+  void EmitDecompositionVarInit(const DecompositionDecl &var);
+
   /// Emits the alloca and debug information for the size expressions for each
   /// dimension of an array. It registers the association of its (1-dimensional)
   /// QualTypes and size expression's debug node, so that CGDebugInfo can
@@ -5180,7 +5182,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock,
                             llvm::BasicBlock *FalseBlock, uint64_t TrueCount,
                             Stmt::Likelihood LH = Stmt::LH_None,
-                            const Expr *ConditionalOp = nullptr);
+                            const Expr *ConditionalOp = nullptr,
+                            const VarDecl *ConditionalDecl = nullptr);
 
   /// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is
   /// nonnull, if \p LHS is marked _Nonnull.
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 36de02d91e151..28215332f5ca3 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7768,9 +7768,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
       NewVD = cast<VarDecl>(Res.get());
       AddToScope = false;
     } else if (D.isDecompositionDeclarator()) {
-      NewVD = DecompositionDecl::Create(Context, DC, D.getBeginLoc(),
-                                        D.getIdentifierLoc(), R, TInfo, SC,
-                                        Bindings);
+      NewVD = DecompositionDecl::Create(
+          Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), R, TInfo, SC,
+          Bindings, D.getContext() == DeclaratorContext::Condition);
     } else
       NewVD = VarDecl::Create(Context, DC, D.getBeginLoc(),
                               D.getIdentifierLoc(), II, R, TInfo, SC);
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a3a028b9485d6..0e6017b75cfd8 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -740,13 +740,16 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
     return nullptr;
   }
 
-  Diag(Decomp.getLSquareLoc(),
-       !getLangOpts().CPlusPlus17
-           ? diag::ext_decomp_decl
-           : D.getContext() == DeclaratorContext::Condition
-                 ? diag::ext_decomp_decl_cond
-                 : diag::warn_cxx14_compat_decomp_decl)
-      << Decomp.getSourceRange();
+  unsigned DiagID;
+  if (!getLangOpts().CPlusPlus17)
+    DiagID = diag::ext_decomp_decl;
+  else if (D.getContext() == DeclaratorContext::Condition) {
+    DiagID = getLangOpts().CPlusPlus26 ? diag::warn_cxx26_decomp_decl_cond
+                                       : diag::ext_decomp_decl_cond;
+  } else
+    DiagID = diag::warn_cxx14_compat_decomp_decl;
+
+  Diag(Decomp.getLSquareLoc(), DiagID) << Decomp.getSourceRange();
 
   // The semantic context is always just the current context.
   DeclContext *const DC = CurContext;
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index dd894df851488..c87ce2ae15ae9 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -14,6 +14,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTLambda.h"
 #include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/DependentDiagnostic.h"
 #include "clang/AST/Expr.h"
@@ -1473,9 +1474,10 @@ Decl *TemplateDeclInstantiator::VisitVarDecl(VarDecl *D,
   // Build the instantiated declaration.
   VarDecl *Var;
   if (Bindings)
-    Var = DecompositionDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(),
-                                    D->getLocation(), DI->getType(), DI,
-                                    D->getStorageClass(), *Bindings);
+    Var = DecompositionDecl::Create(
+        SemaRef.Context, DC, D->getInnerLocStart(), D->getLocation(),
+        DI->getType(), DI, D->getStorageClass(), *Bindings,
+        cast<DecompositionDecl>(D)->isDecisionVariable());
   else
     Var = VarDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(),
                           D->getLocation(), D->getIdentifier(), DI->getType(),
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 2a580c44b94e5..113ed96ea2021 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4115,9 +4115,12 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
   case DECL_PARM_VAR:
     D = ParmVarDecl::CreateDeserialized(Context, ID);
     break;
-  case DECL_DECOMPOSITION:
-    D = DecompositionDecl::CreateDeserialized(Context, ID, Record.readInt());
-    break;
+  case DECL_DECOMPOSITION: {
+    unsigned NumBindings = Record.readInt();
+    bool IsDecisionVariable = Record.readBool();
+    D = DecompositionDecl::CreateDeserialized(Context, ID, NumBindings,
+                                              IsDecisionVariable);
+  } break;
   case DECL_BINDING:
     D = BindingDecl::CreateDeserialized(Context, ID);
     break;
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index a1810003f5425..c11866319f2b0 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1327,6 +1327,7 @@ void ASTDeclWriter::VisitParmVarDecl(ParmVarDecl *D) {
 void ASTDeclWriter::VisitDecompositionDecl(DecompositionDecl *D) {
   // Record the number of bindings first to simplify deserialization.
   Record.push_back(D->bindings().size());
+  Record.push_back(D->isDecisionVariable());
 
   VisitVarDecl(D);
   for (auto *B : D->bindings())
diff --git a/clang/test/AST/ByteCode/if.cpp b/clang/test/AST/ByteCode/if.cpp
index c48b2b8d378c8..909e08a22a283 100644
--- a/clang/test/AST/ByteCode/if.cpp
+++ b/clang/test/AST/ByteCode/if.cpp
@@ -54,7 +54,7 @@ namespace InitDecl {
 constexpr char g(char const (&x)[2]) {
     return 'x';
   if (auto [a, b] = x) // both-error {{an array type is not allowed here}} \
-                       // both-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+                       // both-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     ;
 }
 static_assert(g("x") == 'x');
diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp
index acf3f99069185..96e25e8d9593b 100644
--- a/clang/test/Parser/cxx1z-decomposition.cpp
+++ b/clang/test/Parser/cxx1z-decomposition.cpp
@@ -37,12 +37,12 @@ namespace OtherDecl {
   void g() {
     // A condition is allowed as a Clang extension.
     // See commentary in test/Parser/decomposed-condition.cpp
-    for (; auto [a, b, c] = S(); ) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
-    if (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
-    if (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
-    switch (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}}
-    switch (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}}
-    while (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
+    for (; auto [a, b, c] = S(); ) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
+    if (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
+    if (int n; auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
+    switch (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('S' invalid)}}
+    switch (int n; auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('S' invalid)}}
+    while (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}}
 
     // An exception-declaration is not a simple-declaration.
     try {}
diff --git a/clang/test/Parser/decomposed-condition.cpp b/clang/test/Parser/decomposed-condition.cpp
index 4c635c4735db8..37a24f8f95093 100644
--- a/clang/test/Parser/decomposed-condition.cpp
+++ b/clang/test/Parser/decomposed-condition.cpp
@@ -33,33 +33,33 @@ Na g();
 
 namespace CondInIf {
 int h() {
-  if (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  if (auto [ok, d] = f()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     ;
-  if (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
+  if (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
     ;
-  if (auto [value] = Get()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  if (auto [value] = Get()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     return value;
 }
 } // namespace CondInIf
 
 namespace CondInWhile {
 int h() {
-  while (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  while (auto [ok, d] = f()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     ;
-  while (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
+  while (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
     ;
-  while (auto [value] = Get()) // expected-warning{{ISO C++17 does not permit structured binding declaration in a condition}}
+  while (auto [value] = Get()) // expected-warning{{structured binding declaration in a condition is a C++2c extenstion}}
     return value;
 }
 } // namespace CondInWhile
 
 namespace CondInFor {
 int h() {
-  for (; auto [ok, d] = f();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  for (; auto [ok, d] = f();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     ;
-  for (; auto [ok, d] = g();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
+  for (; auto [ok, d] = g();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}}
     ;
-  for (; auto [value] = Get();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  for (; auto [value] = Get();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     return value;
 }
 } // namespace CondInFor
@@ -74,11 +74,11 @@ struct IntegerLike {
 
 namespace CondInSwitch {
 int h(IntegerLike x) {
-  switch (auto [ok, d] = x) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  switch (auto [ok, d] = x) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
     ;
-  switch (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('Na' invalid)}}
+  switch (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('Na' invalid)}}
     ;
-  switch (auto [value] = Get()) {// expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}}
+  switch (auto [value] = Get()) {// expected-warning {{structured binding declaration in a condition is a C++2c extenstion}}
   // expected-warning at -1{{switch condition has boolean value}}
   case 1:
     return value;
diff --git a/clang/test/SemaCXX/decomposed-condition.cpp b/clang/test/SemaCXX/decomposed-condition.cpp
index e55bbee3134ca..32ab3a34f567b 100644
--- a/clang/test/SemaCXX/decomposed-condition.cpp
+++ b/clang/test/SemaCXX/decomposed-condition.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -std=c++1z -Wno-binding-in-condition -verify %s
-// RUN: %clang_cc1 -std=c++1z -Wno-binding-in-condition -verify %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s
+// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter
 
 struct X {
   bool flag;

>From 5cd91f8adf02d633e7503b144a2d2b264d50dcaf Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 9 Mar 2025 23:29:02 +0800
Subject: [PATCH 2/4] Add a simple CG test, release note, comments for decision
 variable [dcl.struct.bind]

---
 clang/docs/ReleaseNotes.rst       |   2 +
 clang/include/clang/AST/DeclCXX.h |   4 ++
 clang/lib/CodeGen/CGStmt.cpp      |  21 ++++++
 clang/lib/Sema/SemaDecl.cpp       |   3 +-
 clang/test/CodeGen/p0963r3.cpp    | 108 ++++++++++++++++++++++++++++++
 clang/www/cxx_status.html         |   2 +-
 6 files changed, 138 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGen/p0963r3.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 37ea963bf337d..bea43ebcedb2f 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -76,6 +76,8 @@ C++2c Feature Support
 
 - Implemented `P1061R10 Structured Bindings can introduce a Pack <https://wg21.link/P1061R10>`_.
 
+- Implemented `P0963R3 Structured binding declaration as a condition <https://wg21.link/P0963R3>`_.
+
 C++23 Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 414a5aea32566..d82d9900945a1 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -4290,6 +4290,10 @@ class DecompositionDecl final
                                             std::move(Bindings));
   }
 
+  /// The decision variable of a condition that is a structured binding
+  /// declaration is specified in [dcl.struct.bind]p4:
+  ///   If a structured binding declaration appears as a condition, the decision
+  ///   variable of the condition is e.
   bool isDecisionVariable() const { return IsDecisionVariable; }
 
   void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override;
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 1d71f22dc2d47..0a49eadbf33d1 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1110,6 +1110,11 @@ void CodeGenFunction::EmitWhileStmt(const WhileStmt &S,
   // execution of the loop body.
   llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond());
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable());
+      DD && DD->isDecisionVariable())
+    EmitDecompositionVarInit(*DD);
+
   // while(1) is common, avoid extra exit blocks.  Be sure
   // to correctly handle break/continue though.
   llvm::ConstantInt *C = dyn_cast<llvm::ConstantInt>(BoolCondVal);
@@ -1343,6 +1348,12 @@ void CodeGenFunction::EmitForStmt(const ForStmt &S,
     // C99 6.8.5p2/p4: The first substatement is executed if the expression
     // compares unequal to 0.  The condition must be a scalar type.
     llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond());
+
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable());
+        DD && DD->isDecisionVariable())
+      EmitDecompositionVarInit(*DD);
+
     llvm::MDNode *Weights =
         createProfileWeightsForLoop(S.getCond(), getProfileCount(S.getBody()));
     if (!Weights && CGM.getCodeGenOpts().OptimizationLevel)
@@ -2240,6 +2251,11 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
       if (S.getConditionVariable())
         EmitDecl(*S.getConditionVariable());
 
+      if (auto *DD =
+              dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable());
+          DD && DD->isDecisionVariable())
+        EmitDecompositionVarInit(*DD);
+
       // At this point, we are no longer "within" a switch instance, so
       // we can temporarily enforce this to ensure that any embedded case
       // statements are not emitted.
@@ -2271,6 +2287,11 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
     EmitDecl(*S.getConditionVariable());
   llvm::Value *CondV = EmitScalarExpr(S.getCond());
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable());
+      DD && DD->isDecisionVariable())
+    EmitDecompositionVarInit(*DD);
+
   // Create basic block to hold stuff that comes after switch
   // statement. We also need to create a default block now so that
   // explicit case ranges tests can have a place to jump to on
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 28215332f5ca3..675a928551fcb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7770,7 +7770,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     } else if (D.isDecompositionDeclarator()) {
       NewVD = DecompositionDecl::Create(
           Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), R, TInfo, SC,
-          Bindings, D.getContext() == DeclaratorContext::Condition);
+          Bindings, /*IsDecisionVariable=*/D.getContext() ==
+                        DeclaratorContext::Condition);
     } else
       NewVD = VarDecl::Create(Context, DC, D.getBeginLoc(),
                               D.getIdentifierLoc(), II, R, TInfo, SC);
diff --git a/clang/test/CodeGen/p0963r3.cpp b/clang/test/CodeGen/p0963r3.cpp
new file mode 100644
index 0000000000000..64f97feb28ae1
--- /dev/null
+++ b/clang/test/CodeGen/p0963r3.cpp
@@ -0,0 +1,108 @@
+// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm %s -o - | FileCheck %s
+// expected-no-diagnostics
+
+namespace std {
+
+template <typename T> struct tuple_size;
+
+template <int, typename> struct tuple_element;
+
+} // namespace std
+
+namespace Case1 {
+
+struct S {
+  int a, b;
+  bool called_operator_bool = false;
+
+  operator bool() {
+    called_operator_bool = true;
+    return a != b;
+  }
+
+  template <int I> int get() {
+    if (!called_operator_bool)
+      return a + b;
+    return I == 0 ? a : b;
+  }
+};
+
+} // namespace Case1
+
+template <> struct std::tuple_size<Case1::S> {
+  static const int value = 2;
+};
+
+template <int I> struct std::tuple_element<I, Case1::S> {
+  using type = int;
+};
+
+namespace Case1 {
+
+void foo() {
+  if (S s(1, 2); auto [a, b] = s) {
+    __builtin_assume(a == 1);
+    __builtin_assume(b == 2);
+  }
+// CHECK: %[[call:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call]], label {{.*}}, label {{.*}}
+
+  if (auto [a, b] = S(1, 2)) {
+    __builtin_assume(a == 1);
+    __builtin_assume(b == 2);
+  }
+// CHECK: %[[call2:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call2]], label {{.*}}, label {{.*}}
+
+  if (S s(3, 4); auto& [a, b] = s) {
+    __builtin_assume(a == 3);
+    __builtin_assume(b == 4);
+  }
+// CHECK: %[[call3:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}}
+
+  while (auto [i, j] = S(5, 6))
+    break;
+
+// CHECK: while.cond{{.*}}:
+// CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call4]], label {{.*}}, label {{.*}}
+
+  S s(7, 8);
+  while (auto& [i, j] = s)
+    break;
+
+// CHECK: while.cond{{.*}}:
+// CHECK: %[[call5:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call5]], label {{.*}}, label {{.*}}
+
+  for (int k = 0; auto [i, j] = S(24, 42); ++k)
+    break;
+
+// CHECK: for.cond{{.*}}:
+// CHECK: %[[call6:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call6]], label {{.*}}, label {{.*}}
+
+  for (S s(114, 514); auto& [i, j] = s; ++i)
+    break;
+
+// CHECK: for.cond{{.*}}:
+// CHECK: %[[call7:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
+// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: br i1 %[[call7]], label {{.*}}, label {{.*}}
+}
+
+} // namespace Case1
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 8570592101cc5..3f652c4291ddd 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -223,7 +223,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Structured binding declaration as a condition</td>
   <td><a href="https://wg21.link/P0963R3">P0963R3</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 21</td>
  </tr>
  <!--Poland, Fall 2024-->
  <tr>

>From ffe3922efc996e017b9425f22fe7953f352e12b6 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sun, 9 Mar 2025 23:38:06 +0800
Subject: [PATCH 3/4] Test -Wpre-c++26-compat

---
 clang/test/SemaCXX/decomposed-condition.cpp | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/clang/test/SemaCXX/decomposed-condition.cpp b/clang/test/SemaCXX/decomposed-condition.cpp
index 32ab3a34f567b..14a23af3f5658 100644
--- a/clang/test/SemaCXX/decomposed-condition.cpp
+++ b/clang/test/SemaCXX/decomposed-condition.cpp
@@ -1,5 +1,7 @@
-// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s
-// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++17 -Wno-c++26-extensions -verify %s
+// RUN: %clang_cc1 -std=c++17 -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++2c -Wpre-c++26-compat -verify=cxx26,expected %s
+// RUN: %clang_cc1 -std=c++2c -Wpre-c++26-compat -verify=cxx26,expected %s -fexperimental-new-constant-interpreter
 
 struct X {
   bool flag;
@@ -14,7 +16,7 @@ struct X {
 
 namespace CondInIf {
 constexpr int f(X x) {
-  if (auto [ok, d] = x)
+  if (auto [ok, d] = x) // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     return d + int(ok);
   else
     return d * int(ok);
@@ -26,12 +28,13 @@ static_assert(f({true, 2}) == 3);
 static_assert(f({false, 2}) == 0);
 
 constexpr char g(char const (&x)[2]) {
-  if (auto &[a, b] = x)
+  if (auto &[a, b] = x) // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     return a;
   else
     return b;
 
-  if (auto [a, b] = x) // expected-error {{an array type is not allowed here}}
+  if (auto [a, b] = x) // expected-error {{an array type is not allowed here}} \
+                       // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     ;
 }
 
@@ -41,6 +44,7 @@ static_assert(g("x") == 'x');
 namespace CondInSwitch {
 constexpr int f(int n) {
   switch (X s = {true, n}; auto [ok, d] = s) {
+    // cxx26-warning at -1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     s = {};
   case 0:
     return int(ok);
@@ -65,6 +69,7 @@ namespace CondInWhile {
 constexpr int f(int n) {
   int m = 1;
   while (auto [ok, d] = X{n > 1, n}) {
+    // cxx26-warning at -1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     m *= d;
     --n;
   }
@@ -81,6 +86,7 @@ namespace CondInFor {
 constexpr int f(int n) {
   int a = 1, b = 1;
   for (X x = {true, n}; auto &[ok, d] = x; --d) {
+    // cxx26-warning at -1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}}
     if (d < 2)
       ok = false;
     else {

>From 2ae72b4710e036a3b19ac06b0feee96d13a68092 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 10 Mar 2025 13:56:32 +0800
Subject: [PATCH 4/4] Add more tests; handle switch (..) case .. in constant
 evaluator

---
 clang/lib/AST/ExprConstant.cpp |   6 ++
 clang/test/CodeGen/p0963r3.cpp | 145 ++++++++++++++++++++++++++++-----
 2 files changed, 130 insertions(+), 21 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 564a47a839336..898f8a2fce769 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5351,6 +5351,12 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info,
     if (!EvaluateInteger(SS->getCond(), Value, Info))
       return ESR_Failed;
 
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(SS->getConditionVariable());
+        DD && DD->isDecisionVariable() &&
+        !EvaluateDecompositionDeclInit(Info, DD))
+      return ESR_Failed;
+
     if (!CondScope.destroy())
       return ESR_Failed;
   }
diff --git a/clang/test/CodeGen/p0963r3.cpp b/clang/test/CodeGen/p0963r3.cpp
index 64f97feb28ae1..b356e78adfa83 100644
--- a/clang/test/CodeGen/p0963r3.cpp
+++ b/clang/test/CodeGen/p0963r3.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm -triple=x86_64-pc-linux-gnu %s -o - | FileCheck %s
 // expected-no-diagnostics
 
 namespace std {
@@ -13,16 +13,26 @@ namespace Case1 {
 
 struct S {
   int a, b;
-  bool called_operator_bool = false;
+  bool flag = false;
 
-  operator bool() {
-    called_operator_bool = true;
+  constexpr explicit operator bool() {
+    flag = true;
     return a != b;
   }
 
-  template <int I> int get() {
-    if (!called_operator_bool)
-      return a + b;
+  constexpr operator int() {
+    flag = true;
+    return a * b;
+  }
+
+  constexpr bool operator==(S rhs) const {
+    return a == rhs.a && b == rhs.b;
+  }
+
+  template <int I>
+  constexpr int& get() {
+    if (!flag)
+      return a = a + b;
     return I == 0 ? a : b;
   }
 };
@@ -45,8 +55,8 @@ void foo() {
     __builtin_assume(b == 2);
   }
 // CHECK: %[[call:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call]], label {{.*}}, label {{.*}}
 
   if (auto [a, b] = S(1, 2)) {
@@ -54,8 +64,8 @@ void foo() {
     __builtin_assume(b == 2);
   }
 // CHECK: %[[call2:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call2]], label {{.*}}, label {{.*}}
 
   if (S s(3, 4); auto& [a, b] = s) {
@@ -63,8 +73,8 @@ void foo() {
     __builtin_assume(b == 4);
   }
 // CHECK: %[[call3:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}}
 
   while (auto [i, j] = S(5, 6))
@@ -72,8 +82,8 @@ void foo() {
 
 // CHECK: while.cond{{.*}}:
 // CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call4]], label {{.*}}, label {{.*}}
 
   S s(7, 8);
@@ -82,8 +92,8 @@ void foo() {
 
 // CHECK: while.cond{{.*}}:
 // CHECK: %[[call5:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call5]], label {{.*}}, label {{.*}}
 
   for (int k = 0; auto [i, j] = S(24, 42); ++k)
@@ -91,8 +101,8 @@ void foo() {
 
 // CHECK: for.cond{{.*}}:
 // CHECK: %[[call6:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call6]], label {{.*}}, label {{.*}}
 
   for (S s(114, 514); auto& [i, j] = s; ++i)
@@ -100,9 +110,102 @@ void foo() {
 
 // CHECK: for.cond{{.*}}:
 // CHECK: %[[call7:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv
-// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
 // CHECK: br i1 %[[call7]], label {{.*}}, label {{.*}}
+
+  switch (S s(10, 11); auto& [i, j] = s) {
+    case 10 * 11:
+      __builtin_assume(i == 10);
+      __builtin_assume(j == 11);
+      break;
+    default:
+      break;
+  }
+
+// CHECK: %[[call8:.+]] = call {{.*}} i32 @_ZN5Case11ScviEv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// CHECK: switch i32 %[[call8]], label {{.*}}
+
 }
 
+constexpr int bar(auto) {
+  constexpr auto value = [] {
+    if (S s(1, 2); auto [i, j] = s)
+      return S(i, j);
+    return S(0, 0);
+  }();
+  static_assert(value == S(1, 2));
+
+  // FIXME: The diagnostic message adds a trailing comma "static assertion failed due to requirement 'value == Case1::S((0, 1, ))'"
+  // static_assert(value == S(0, 1));
+
+  constexpr auto value2 = [] {
+    if (auto [a, b] = S(1, 2))
+      return S(a, b);
+    return S(0, 0);
+  }();
+  static_assert(value2 == S(1, 2));
+
+  constexpr auto value3 = [] {
+    if (auto&& [a, b] = S(3, 4))
+      return S(a, b);
+    return S(0, 0);
+  }();
+  static_assert(value3 == S(3, 4));
+
+  constexpr auto value4 = [] {
+    S s(7, 8);
+    int cnt = 0;
+    while (auto& [i, j] = s) {
+      s.flag = false;
+      ++i, ++j;
+      if (++cnt == 10)
+        break;
+    }
+    return s;
+  }();
+  static_assert(value4 == S(17, 18));
+
+  constexpr auto value5 = [] {
+    S s(3, 4);
+    for (int cnt = 0; auto& [x, y] = s; s.flag = false, ++cnt) {
+      if (cnt == 3)
+        break;
+      ++x, ++y;
+    }
+    return s;
+  }();
+  static_assert(value5 == S(6, 7));
+
+  constexpr auto value6 = [] {
+    switch (auto [x, y] = S(3, 4)) {
+      case 3 * 4:
+        return S(x, y);
+      default:
+        return S(y, x);
+    }
+  }();
+  static_assert(value6 == S(3, 4));
+
+  return 42;
+}
+
+constexpr int value = bar(1);
+
+#if 0
+
+// FIXME: This causes clang to ICE, though this is not a regression.
+constexpr int ice(auto) {
+  if constexpr (S s(1, 2); auto [i, j] = s) {
+    static_assert(i == 1);
+  }
+  return 42;
+}
+
+constexpr int value2 = ice(1);
+
+#endif
+
 } // namespace Case1



More information about the cfe-commits mailing list