Skip to content

[Clang] Implement P0963R3 "Structured binding declaration as a condition" #130228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 11, 2025

Conversation

zyn0217
Copy link
Contributor

@zyn0217 zyn0217 commented Mar 7, 2025

This implements the R2 semantics of P0963.

The R1 semantics, as outlined in the paper, were introduced in Clang 6. In addition to that, the paper proposes swapping the evaluation order of condition expressions and the initialization of binding declarations (i.e. std::tuple-like decompositions).

@cor3ntin cor3ntin added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Mar 7, 2025
Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

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

Looks good so far, thanks!

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

Only a small pair of nits/questions so far, but otherwise this is on the right track.

bool OK = true;
for (auto *BD : DD->flat_bindings())
if (auto *VD = BD->getHoldingVar())
OK &= EvaluateDecl(Info, VD);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to short-circuit here instead? We could early return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can, at least we have been doing so in the bytecode evaluator; though I'm not sure why there's discrepancy in the old constant evaluator

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Erich

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't bail out early here: EvaluateVarDecl() might fail because EvaluateInPlace() might fail, and in that case, we still need to continue evaluating the initializers; otherwise, we'll crash. Here is an example

namespace std {

template <typename T> struct tuple_size;

template <int, typename> struct tuple_element;

} // namespace std


namespace constant {
  struct Q {};
  template<int N> constexpr int get(Q &&) { return N * N; }
}
template<> struct std::tuple_size<constant::Q> { static const int value = 3; };
template<int N> struct std::tuple_element<N, constant::Q> { typedef int type; };

namespace constant {
Q q;

constexpr bool f() {
  auto [a, b, c] = q;
  return a == 0 && b == 1 && c == 4;
}
static_assert(f());
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(The crash doesn't occur in C++23 mode)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And we crash during isPotentialConstantExpr(), wherein we set CheckingPotentialConstantExpression = true that results in EvaluateVarDecl() returning true because EvaluateInPlace() it called bailed out of evaluation in this case. So in short, we can't short-circuit the initialization of the binding decls because they're necessary in CheckingPotentialConstantExpression.

@zyn0217 zyn0217 marked this pull request as ready for review March 10, 2025 08:08
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:modules C++20 modules and Clang Header Modules clang:codegen IR generation bugs: mangling, exceptions, etc. labels Mar 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 10, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang-modules

Author: Younan Zhang (zyn0217)

Changes

Todo:

  • Adapt the bytecode interpreter to the new evaluation model

  • Tests for CG & Constant evaluation

  • Release notes

With this patch, the example shown in https://gcc.godbolt.org/z/3h74oq8zW (the R2 semantics) works now.


Patch is 41.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/130228.diff

23 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang/AST/DeclCXX.h (+19-8)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6-2)
  • (modified) clang/lib/AST/ASTImporter.cpp (+4-3)
  • (modified) clang/lib/AST/ByteCode/Compiler.cpp (+39-6)
  • (modified) clang/lib/AST/ByteCode/Compiler.h (+1)
  • (modified) clang/lib/AST/DeclCXX.cpp (+12-9)
  • (modified) clang/lib/AST/ExprConstant.cpp (+26-4)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+9-4)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+35-3)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+6-1)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+4-1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+4-3)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+10-7)
  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+5-3)
  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+6-3)
  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+1)
  • (modified) clang/test/AST/ByteCode/if.cpp (+1-1)
  • (added) clang/test/CodeGen/p0963r3.cpp (+212)
  • (modified) clang/test/Parser/cxx1z-decomposition.cpp (+6-6)
  • (modified) clang/test/Parser/decomposed-condition.cpp (+12-12)
  • (modified) clang/test/SemaCXX/decomposed-condition.cpp (+11-5)
  • (modified) clang/www/cxx_status.html (+1-1)
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 dbd02ef7f8011..d82d9900945a1 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,12 @@ 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;
 
   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/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 394e39e99a106..213e1a965ff44 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5104,6 +5104,16 @@ bool Compiler<Emitter>::visitCompoundStmt(const CompoundStmt *S) {
   return Scope.destroyLocals();
 }
 
+template <class Emitter>
+bool Compiler<Emitter>::emitDecompositionVarInit(const DecompositionDecl *DD) {
+  for (auto *BD : DD->bindings())
+    if (auto *KD = BD->getHoldingVar()) {
+      if (!this->visitVarDecl(KD))
+        return false;
+    }
+  return true;
+}
+
 template <class Emitter>
 bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS) {
   for (const auto *D : DS->decls()) {
@@ -5118,12 +5128,10 @@ bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS) {
       return false;
 
     // Register decomposition decl holding vars.
-    if (const auto *DD = dyn_cast<DecompositionDecl>(VD)) {
-      for (auto *BD : DD->bindings())
-        if (auto *KD = BD->getHoldingVar()) {
-          if (!this->visitVarDecl(KD))
-            return false;
-        }
+    if (const auto *DD = dyn_cast<DecompositionDecl>(VD);
+        DD && !DD->isDecisionVariable()) {
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
     }
   }
 
@@ -5189,6 +5197,12 @@ template <class Emitter> bool Compiler<Emitter>::visitIfStmt(const IfStmt *IS) {
       return false;
   }
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(IS->getConditionVariable());
+      DD && DD->isDecisionVariable())
+    if (!this->emitDecompositionVarInit(DD))
+      return false;
+
   if (const Stmt *Else = IS->getElse()) {
     LabelTy LabelElse = this->getLabel();
     LabelTy LabelEnd = this->getLabel();
@@ -5249,6 +5263,13 @@ bool Compiler<Emitter>::visitWhileStmt(const WhileStmt *S) {
 
     if (!this->visitBool(Cond))
       return false;
+
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+        DD && DD->isDecisionVariable())
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
+
     if (!this->jumpFalse(EndLabel))
       return false;
 
@@ -5330,6 +5351,12 @@ bool Compiler<Emitter>::visitForStmt(const ForStmt *S) {
         return false;
     }
 
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+        DD && DD->isDecisionVariable())
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
+
     if (Body && !this->visitStmt(Body))
       return false;
 
@@ -5452,6 +5479,12 @@ bool Compiler<Emitter>::visitSwitchStmt(const SwitchStmt *S) {
   if (!this->emitSetLocal(CondT, CondVar, S))
     return false;
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+      DD && DD->isDecisionVariable())
+    if (!this->emitDecompositionVarInit(DD))
+      return false;
+
   CaseMap CaseLabels;
   // Create labels and comparison ops for all case statements.
   for (const SwitchCase *SC = S->getSwitchCaseList(); SC;
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 77fcc3d1b41ce..9bb0ba6e52f16 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -386,6 +386,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
   bool compileUnionAssignmentOperator(const CXXMethodDecl *MD);
 
   bool checkLiteralType(const Expr *E);
+  bool emitDecompositionVarInit(const DecompositionDecl *DD);
 
 protected:
   /// Variable to storage mapping.
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..898f8a2fce769 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();
 }
 
@@ -5335,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/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..0a49eadbf33d1 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);
   }
 
@@ -1099,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);
@@ -1332,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)
@@ -2229,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.
@@ -2260,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/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::BasicB...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 10, 2025

@llvm/pr-subscribers-clang

Author: Younan Zhang (zyn0217)

Changes

Todo:

  • Adapt the bytecode interpreter to the new evaluation model

  • Tests for CG & Constant evaluation

  • Release notes

With this patch, the example shown in https://gcc.godbolt.org/z/3h74oq8zW (the R2 semantics) works now.


Patch is 41.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/130228.diff

23 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang/AST/DeclCXX.h (+19-8)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6-2)
  • (modified) clang/lib/AST/ASTImporter.cpp (+4-3)
  • (modified) clang/lib/AST/ByteCode/Compiler.cpp (+39-6)
  • (modified) clang/lib/AST/ByteCode/Compiler.h (+1)
  • (modified) clang/lib/AST/DeclCXX.cpp (+12-9)
  • (modified) clang/lib/AST/ExprConstant.cpp (+26-4)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+9-4)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+35-3)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+6-1)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+4-1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+4-3)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+10-7)
  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+5-3)
  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+6-3)
  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+1)
  • (modified) clang/test/AST/ByteCode/if.cpp (+1-1)
  • (added) clang/test/CodeGen/p0963r3.cpp (+212)
  • (modified) clang/test/Parser/cxx1z-decomposition.cpp (+6-6)
  • (modified) clang/test/Parser/decomposed-condition.cpp (+12-12)
  • (modified) clang/test/SemaCXX/decomposed-condition.cpp (+11-5)
  • (modified) clang/www/cxx_status.html (+1-1)
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 dbd02ef7f8011..d82d9900945a1 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,12 @@ 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;
 
   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/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 394e39e99a106..213e1a965ff44 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5104,6 +5104,16 @@ bool Compiler<Emitter>::visitCompoundStmt(const CompoundStmt *S) {
   return Scope.destroyLocals();
 }
 
+template <class Emitter>
+bool Compiler<Emitter>::emitDecompositionVarInit(const DecompositionDecl *DD) {
+  for (auto *BD : DD->bindings())
+    if (auto *KD = BD->getHoldingVar()) {
+      if (!this->visitVarDecl(KD))
+        return false;
+    }
+  return true;
+}
+
 template <class Emitter>
 bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS) {
   for (const auto *D : DS->decls()) {
@@ -5118,12 +5128,10 @@ bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS) {
       return false;
 
     // Register decomposition decl holding vars.
-    if (const auto *DD = dyn_cast<DecompositionDecl>(VD)) {
-      for (auto *BD : DD->bindings())
-        if (auto *KD = BD->getHoldingVar()) {
-          if (!this->visitVarDecl(KD))
-            return false;
-        }
+    if (const auto *DD = dyn_cast<DecompositionDecl>(VD);
+        DD && !DD->isDecisionVariable()) {
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
     }
   }
 
@@ -5189,6 +5197,12 @@ template <class Emitter> bool Compiler<Emitter>::visitIfStmt(const IfStmt *IS) {
       return false;
   }
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(IS->getConditionVariable());
+      DD && DD->isDecisionVariable())
+    if (!this->emitDecompositionVarInit(DD))
+      return false;
+
   if (const Stmt *Else = IS->getElse()) {
     LabelTy LabelElse = this->getLabel();
     LabelTy LabelEnd = this->getLabel();
@@ -5249,6 +5263,13 @@ bool Compiler<Emitter>::visitWhileStmt(const WhileStmt *S) {
 
     if (!this->visitBool(Cond))
       return false;
+
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+        DD && DD->isDecisionVariable())
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
+
     if (!this->jumpFalse(EndLabel))
       return false;
 
@@ -5330,6 +5351,12 @@ bool Compiler<Emitter>::visitForStmt(const ForStmt *S) {
         return false;
     }
 
+    if (auto *DD =
+            dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+        DD && DD->isDecisionVariable())
+      if (!this->emitDecompositionVarInit(DD))
+        return false;
+
     if (Body && !this->visitStmt(Body))
       return false;
 
@@ -5452,6 +5479,12 @@ bool Compiler<Emitter>::visitSwitchStmt(const SwitchStmt *S) {
   if (!this->emitSetLocal(CondT, CondVar, S))
     return false;
 
+  if (auto *DD =
+          dyn_cast_if_present<DecompositionDecl>(S->getConditionVariable());
+      DD && DD->isDecisionVariable())
+    if (!this->emitDecompositionVarInit(DD))
+      return false;
+
   CaseMap CaseLabels;
   // Create labels and comparison ops for all case statements.
   for (const SwitchCase *SC = S->getSwitchCaseList(); SC;
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 77fcc3d1b41ce..9bb0ba6e52f16 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -386,6 +386,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
   bool compileUnionAssignmentOperator(const CXXMethodDecl *MD);
 
   bool checkLiteralType(const Expr *E);
+  bool emitDecompositionVarInit(const DecompositionDecl *DD);
 
 protected:
   /// Variable to storage mapping.
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..898f8a2fce769 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();
 }
 
@@ -5335,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/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..0a49eadbf33d1 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);
   }
 
@@ -1099,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);
@@ -1332,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)
@@ -2229,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.
@@ -2260,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/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::BasicB...
[truncated]

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.

I have some questions

Can you edit
https://clang.llvm.org/docs/LanguageExtensions.html#language-extensions-back-ported-to-previous-standards ?

(For people following at home, CWG decided not to provide a feature test macro)

bool OK = true;
for (auto *BD : DD->flat_bindings())
if (auto *VD = BD->getHoldingVar())
OK &= EvaluateDecl(Info, VD);
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Erich

@cor3ntin
Copy link
Contributor

(Make sure to update the description at some point)

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.

I much prefer this approach, thanks!

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.

Thanks

Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

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

LGTM, sans a few nitty nits.

Thanks!

@cor3ntin
Copy link
Contributor

Hum, do we want to add tests for https://cplusplus.github.io/CWG/issues/2867.html at the same time?

@zyn0217 zyn0217 merged commit f421875 into llvm:main Mar 11, 2025
12 checks passed
@zyn0217 zyn0217 deleted the P0963 branch March 11, 2025 07:42
@zyn0217
Copy link
Contributor Author

zyn0217 commented Mar 11, 2025

Hum, do we want to add tests for https://cplusplus.github.io/CWG/issues/2867.html at the same time?

I'll look into that in a followup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants