[clang] f421875 - [Clang] Implement P0963R3 "Structured binding declaration as a condition" (#130228)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 11 00:41:59 PDT 2025
Author: Younan Zhang
Date: 2025-03-11T15:41:56+08:00
New Revision: f4218753ad93dd44b019e38bae61dceb93514aee
URL: https://github.com/llvm/llvm-project/commit/f4218753ad93dd44b019e38bae61dceb93514aee
DIFF: https://github.com/llvm/llvm-project/commit/f4218753ad93dd44b019e38bae61dceb93514aee.diff
LOG: [Clang] Implement P0963R3 "Structured binding declaration as a condition" (#130228)
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).
Added:
clang/test/CodeGen/p0963r3.cpp
Modified:
clang/docs/LanguageExtensions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/AST/ByteCode/Compiler.cpp
clang/lib/AST/ByteCode/Compiler.h
clang/lib/AST/ExprConstant.cpp
clang/lib/CodeGen/CGDecl.cpp
clang/lib/CodeGen/CGStmt.cpp
clang/lib/CodeGen/CodeGenFunction.cpp
clang/lib/CodeGen/CodeGenFunction.h
clang/lib/Sema/SemaDeclCXX.cpp
clang/test/AST/ByteCode/if.cpp
clang/test/Parser/cxx1z-decomposition.cpp
clang/test/Parser/decomposed-condition.cpp
clang/test/SemaCXX/decomposed-condition.cpp
clang/www/cxx_status.html
Removed:
################################################################################
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 578ee02f09b9b..f8d1bc9fe2624 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1604,53 +1604,54 @@ More information could be found `here <https://clang.llvm.org/docs/Modules.html>
Language Extensions Back-ported to Previous Standards
=====================================================
-============================================ ================================ ============= =============
-Feature Feature Test Macro Introduced In Backported To
-============================================ ================================ ============= =============
-variadic templates __cpp_variadic_templates C++11 C++03
-Alias templates __cpp_alias_templates C++11 C++03
-Non-static data member initializers __cpp_nsdmi C++11 C++03
-Range-based ``for`` loop __cpp_range_based_for C++11 C++03
-RValue references __cpp_rvalue_references C++11 C++03
-Attributes __cpp_attributes C++11 C++03
-Lambdas __cpp_lambdas C++11 C++03
-Generalized lambda captures __cpp_init_captures C++14 C++03
-Generic lambda expressions __cpp_generic_lambdas C++14 C++03
-variable templates __cpp_variable_templates C++14 C++03
-Binary literals __cpp_binary_literals C++14 C++03
-Relaxed constexpr __cpp_constexpr C++14 C++11
-Static assert with no message __cpp_static_assert >= 201411L C++17 C++11
-Pack expansion in generalized lambda-capture __cpp_init_captures C++17 C++03
-``if constexpr`` __cpp_if_constexpr C++17 C++11
-fold expressions __cpp_fold_expressions C++17 C++03
-Lambda capture of \*this by value __cpp_capture_star_this C++17 C++03
-Attributes on enums __cpp_enumerator_attributes C++17 C++03
-Guaranteed copy elision __cpp_guaranteed_copy_elision C++17 C++03
-Hexadecimal floating literals __cpp_hex_float C++17 C++03
-``inline`` variables __cpp_inline_variables C++17 C++03
-Attributes on namespaces __cpp_namespace_attributes C++17 C++11
-Structured bindings __cpp_structured_bindings C++17 C++03
-template template arguments __cpp_template_template_args C++17 C++03
-Familiar template syntax for generic lambdas __cpp_generic_lambdas C++20 C++03
-``static operator[]`` __cpp_multidimensional_subscript C++20 C++03
-Designated initializers __cpp_designated_initializers C++20 C++03
-Conditional ``explicit`` __cpp_conditional_explicit C++20 C++03
-``using enum`` __cpp_using_enum C++20 C++03
-``if consteval`` __cpp_if_consteval C++23 C++20
-``static operator()`` __cpp_static_call_operator C++23 C++03
-Attributes on Lambda-Expressions C++23 C++11
-Attributes on Structured Bindings __cpp_structured_bindings C++26 C++03
-Packs in Structured Bindings __cpp_structured_bindings C++26 C++03
-Static assert with user-generated message __cpp_static_assert >= 202306L C++26 C++11
-Pack Indexing __cpp_pack_indexing C++26 C++03
-``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
-Variadic Friends __cpp_variadic_friend C++26 C++03
--------------------------------------------- -------------------------------- ------------- -------------
-Designated initializers (N494) C99 C89
-Array & element qualification (N2607) C23 C89
-Attributes (N2335) C23 C89
-``#embed`` (N3017) C23 C89, C++
-============================================ ================================ ============= =============
+============================================= ================================ ============= =============
+Feature Feature Test Macro Introduced In Backported To
+============================================= ================================ ============= =============
+variadic templates __cpp_variadic_templates C++11 C++03
+Alias templates __cpp_alias_templates C++11 C++03
+Non-static data member initializers __cpp_nsdmi C++11 C++03
+Range-based ``for`` loop __cpp_range_based_for C++11 C++03
+RValue references __cpp_rvalue_references C++11 C++03
+Attributes __cpp_attributes C++11 C++03
+Lambdas __cpp_lambdas C++11 C++03
+Generalized lambda captures __cpp_init_captures C++14 C++03
+Generic lambda expressions __cpp_generic_lambdas C++14 C++03
+variable templates __cpp_variable_templates C++14 C++03
+Binary literals __cpp_binary_literals C++14 C++03
+Relaxed constexpr __cpp_constexpr C++14 C++11
+Static assert with no message __cpp_static_assert >= 201411L C++17 C++11
+Pack expansion in generalized lambda-capture __cpp_init_captures C++17 C++03
+``if constexpr`` __cpp_if_constexpr C++17 C++11
+fold expressions __cpp_fold_expressions C++17 C++03
+Lambda capture of \*this by value __cpp_capture_star_this C++17 C++03
+Attributes on enums __cpp_enumerator_attributes C++17 C++03
+Guaranteed copy elision __cpp_guaranteed_copy_elision C++17 C++03
+Hexadecimal floating literals __cpp_hex_float C++17 C++03
+``inline`` variables __cpp_inline_variables C++17 C++03
+Attributes on namespaces __cpp_namespace_attributes C++17 C++11
+Structured bindings __cpp_structured_bindings C++17 C++03
+template template arguments __cpp_template_template_args C++17 C++03
+Familiar template syntax for generic lambdas __cpp_generic_lambdas C++20 C++03
+``static operator[]`` __cpp_multidimensional_subscript C++20 C++03
+Designated initializers __cpp_designated_initializers C++20 C++03
+Conditional ``explicit`` __cpp_conditional_explicit C++20 C++03
+``using enum`` __cpp_using_enum C++20 C++03
+``if consteval`` __cpp_if_consteval C++23 C++20
+``static operator()`` __cpp_static_call_operator C++23 C++03
+Attributes on Lambda-Expressions C++23 C++11
+Attributes on Structured Bindings __cpp_structured_bindings C++26 C++03
+Packs in Structured Bindings __cpp_structured_bindings C++26 C++03
+Structured binding declaration as a condition __cpp_structured_bindings C++26 C++98
+Static assert with user-generated message __cpp_static_assert >= 202306L C++26 C++11
+Pack Indexing __cpp_pack_indexing C++26 C++03
+``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
+Variadic Friends __cpp_variadic_friend C++26 C++03
+--------------------------------------------- -------------------------------- ------------- -------------
+Designated initializers (N494) C99 C89
+Array & element qualification (N2607) C23 C89
+Attributes (N2335) C23 C89
+``#embed`` (N3017) C23 C89, C++
+============================================= ================================ ============= =============
Builtin type aliases
====================
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7d2eea9a7e25f..9e68e23c15580 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -79,6 +79,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/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b73ac2933b1ca..8e6e6e892cdd7 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/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 13b8a3b47add6..b9f88230007b5 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5093,7 +5093,7 @@ template <class Emitter> bool Compiler<Emitter>::visitStmt(const Stmt *S) {
case Stmt::CompoundStmtClass:
return visitCompoundStmt(cast<CompoundStmt>(S));
case Stmt::DeclStmtClass:
- return visitDeclStmt(cast<DeclStmt>(S));
+ return visitDeclStmt(cast<DeclStmt>(S), /*EvaluateConditionDecl=*/true);
case Stmt::ReturnStmtClass:
return visitReturnStmt(cast<ReturnStmt>(S));
case Stmt::IfStmtClass:
@@ -5147,7 +5147,18 @@ bool Compiler<Emitter>::visitCompoundStmt(const CompoundStmt *S) {
}
template <class Emitter>
-bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS) {
+bool Compiler<Emitter>::maybeEmitDeferredVarInit(const VarDecl *VD) {
+ if (auto *DD = dyn_cast_if_present<DecompositionDecl>(VD)) {
+ for (auto *BD : DD->bindings())
+ if (auto *KD = BD->getHoldingVar(); KD && !this->visitVarDecl(KD))
+ return false;
+ }
+ return true;
+}
+
+template <class Emitter>
+bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS,
+ bool EvaluateConditionDecl) {
for (const auto *D : DS->decls()) {
if (isa<StaticAssertDecl, TagDecl, TypedefNameDecl, BaseUsingDecl,
FunctionDecl, NamespaceAliasDecl, UsingDirectiveDecl>(D))
@@ -5160,13 +5171,8 @@ 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 (EvaluateConditionDecl && !this->maybeEmitDeferredVarInit(VD))
+ return false;
}
return true;
@@ -5249,6 +5255,9 @@ template <class Emitter> bool Compiler<Emitter>::visitIfStmt(const IfStmt *IS) {
return false;
}
+ if (!this->maybeEmitDeferredVarInit(IS->getConditionVariable()))
+ return false;
+
if (const Stmt *Else = IS->getElse()) {
LabelTy LabelElse = this->getLabel();
LabelTy LabelEnd = this->getLabel();
@@ -5294,6 +5303,10 @@ bool Compiler<Emitter>::visitWhileStmt(const WhileStmt *S) {
if (!this->visitBool(Cond))
return false;
+
+ if (!this->maybeEmitDeferredVarInit(S->getConditionVariable()))
+ return false;
+
if (!this->jumpFalse(EndLabel))
return false;
@@ -5375,6 +5388,9 @@ bool Compiler<Emitter>::visitForStmt(const ForStmt *S) {
return false;
}
+ if (!this->maybeEmitDeferredVarInit(S->getConditionVariable()))
+ return false;
+
if (Body && !this->visitStmt(Body))
return false;
@@ -5497,6 +5513,9 @@ bool Compiler<Emitter>::visitSwitchStmt(const SwitchStmt *S) {
if (!this->emitSetLocal(CondT, CondVar, S))
return false;
+ if (!this->maybeEmitDeferredVarInit(S->getConditionVariable()))
+ 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 7683d60f869f6..902da8ffdd330 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -213,7 +213,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
// Statements.
bool visitCompoundStmt(const CompoundStmt *S);
- bool visitDeclStmt(const DeclStmt *DS);
+ bool visitDeclStmt(const DeclStmt *DS, bool EvaluateConditionDecl = false);
bool visitReturnStmt(const ReturnStmt *RS);
bool visitIfStmt(const IfStmt *IS);
bool visitWhileStmt(const WhileStmt *S);
@@ -389,6 +389,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool compileUnionAssignmentOperator(const CXXMethodDecl *MD);
bool checkLiteralType(const Expr *E);
+ bool maybeEmitDeferredVarInit(const VarDecl *VD);
protected:
/// Variable to storage mapping.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4649cd79371f4..9ed27c38bc4ea 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5227,20 +5227,41 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
return true;
}
-static bool EvaluateDecl(EvalInfo &Info, const Decl *D) {
- bool OK = true;
+static bool EvaluateDecompositionDeclInit(EvalInfo &Info,
+ const DecompositionDecl *DD);
+static bool EvaluateDecl(EvalInfo &Info, const Decl *D,
+ bool EvaluateConditionDecl = false) {
+ 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);
+ EvaluateConditionDecl && DD)
+ 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, /*EvaluateConditionDecl=*/true);
return OK;
}
+static bool MaybeEvaluateDeferredVarDeclInit(EvalInfo &Info,
+ const VarDecl *VD) {
+ if (auto *DD = dyn_cast_if_present<DecompositionDecl>(VD)) {
+ if (!EvaluateDecompositionDeclInit(Info, DD))
+ return false;
+ }
+ return true;
+}
+
static bool EvaluateDependentExpr(const Expr *E, EvalInfo &Info) {
assert(E->isValueDependent());
if (Info.noteSideEffect())
@@ -5260,6 +5281,8 @@ static bool EvaluateCond(EvalInfo &Info, const VarDecl *CondDecl,
return false;
if (!EvaluateAsBooleanCondition(Cond, Result, Info))
return false;
+ if (!MaybeEvaluateDeferredVarDeclInit(Info, CondDecl))
+ return false;
return Scope.destroy();
}
@@ -5344,6 +5367,9 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info,
if (!EvaluateInteger(SS->getCond(), Value, Info))
return ESR_Failed;
+ if (!MaybeEvaluateDeferredVarDeclInit(Info, SS->getConditionVariable()))
+ return ESR_Failed;
+
if (!CondScope.destroy())
return ESR_Failed;
}
@@ -5568,7 +5594,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
return ESR_Failed;
// Each declaration initialization is its own full-expression.
FullExpressionRAII Scope(Info);
- if (!EvaluateDecl(Info, D) && !Info.noteFailure())
+ if (!EvaluateDecl(Info, D, /*EvaluateConditionDecl=*/true) &&
+ !Info.noteFailure())
return ESR_Failed;
if (!Scope.destroy())
return ESR_Failed;
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 3ad9ebf624143..eab1ebfb2369b 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -48,7 +48,7 @@ using namespace CodeGen;
static_assert(clang::Sema::MaximumAlignment <= llvm::Value::MaximumAlignment,
"Clang max alignment greater than what LLVM supports?");
-void CodeGenFunction::EmitDecl(const Decl &D) {
+void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
switch (D.getKind()) {
case Decl::BuiltinTemplate:
case Decl::TranslationUnit:
@@ -152,7 +152,7 @@ void CodeGenFunction::EmitDecl(const Decl &D) {
return;
case Decl::UsingPack:
for (auto *Using : cast<UsingPackDecl>(D).expansions())
- EmitDecl(*Using);
+ EmitDecl(*Using, /*EvaluateConditionDecl=*/EvaluateConditionDecl);
return;
case Decl::UsingDirective: // using namespace X; [C++]
if (CGDebugInfo *DI = getDebugInfo())
@@ -164,10 +164,8 @@ 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 (EvaluateConditionDecl)
+ MaybeEmitDeferredVarDeclInit(&VD);
return;
}
@@ -2059,6 +2057,14 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
/*IsAutoInit=*/false);
}
+void CodeGenFunction::MaybeEmitDeferredVarDeclInit(const VarDecl *VD) {
+ if (auto *DD = dyn_cast_if_present<DecompositionDecl>(VD)) {
+ 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..99b6f563d7c82 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -913,6 +913,7 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {
if (CondConstant)
incrementProfileCounter(&S);
if (Executed) {
+ MaybeEmitDeferredVarDeclInit(S.getConditionVariable());
RunCleanupsScope ExecutedScope(*this);
EmitStmt(Executed);
}
@@ -952,10 +953,13 @@ 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,
+ /*ConditionalOp=*/nullptr,
+ /*ConditionalDecl=*/S.getConditionVariable());
+ } else {
llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond());
+ MaybeEmitDeferredVarDeclInit(S.getConditionVariable());
Builder.CreateCondBr(BoolCondVal, ThenBlock, ElseBlock);
}
@@ -1099,6 +1103,8 @@ void CodeGenFunction::EmitWhileStmt(const WhileStmt &S,
// execution of the loop body.
llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond());
+ MaybeEmitDeferredVarDeclInit(S.getConditionVariable());
+
// 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 +1338,9 @@ 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());
+
+ MaybeEmitDeferredVarDeclInit(S.getConditionVariable());
+
llvm::MDNode *Weights =
createProfileWeightsForLoop(S.getCond(), getProfileCount(S.getBody()));
if (!Weights && CGM.getCodeGenOpts().OptimizationLevel)
@@ -1662,7 +1671,7 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) {
EmitStopPoint(&S);
for (const auto *I : S.decls())
- EmitDecl(*I);
+ EmitDecl(*I, /*EvaluateConditionDecl=*/true);
}
void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) {
@@ -2227,7 +2236,7 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
// Emit the condition variable if needed inside the entire cleanup scope
// used by this special case for constant folded switches.
if (S.getConditionVariable())
- EmitDecl(*S.getConditionVariable());
+ EmitDecl(*S.getConditionVariable(), /*EvaluateConditionDecl=*/true);
// At this point, we are no longer "within" a switch instance, so
// we can temporarily enforce this to ensure that any embedded case
@@ -2259,6 +2268,7 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
if (S.getConditionVariable())
EmitDecl(*S.getConditionVariable());
llvm::Value *CondV = EmitScalarExpr(S.getCond());
+ MaybeEmitDeferredVarDeclInit(S.getConditionVariable());
// Create basic block to hold stuff that comes after switch
// statement. We also need to create a default block now so that
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 63f0bf533fd45..447192bc7f60c 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,8 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
CondV = EvaluateExprAsBool(Cond);
}
+ MaybeEmitDeferredVarDeclInit(ConditionalDecl);
+
// 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..ca00a0e8c6cf4 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3377,7 +3377,7 @@ class CodeGenFunction : public CodeGenTypeCache {
/// EmitDecl - Emit a declaration.
///
/// This function can be called with a null (unreachable) insert point.
- void EmitDecl(const Decl &D);
+ void EmitDecl(const Decl &D, bool EvaluateConditionDecl = false);
/// EmitVarDecl - Emit a local variable declaration.
///
@@ -3474,6 +3474,8 @@ class CodeGenFunction : public CodeGenTypeCache {
void emitAutoVarTypeCleanup(const AutoVarEmission &emission,
QualType::DestructionKind dtorKind);
+ void MaybeEmitDeferredVarDeclInit(const VarDecl *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/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1edbe62f1c79a..96aac7871db1e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -743,13 +743,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/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/CodeGen/p0963r3.cpp b/clang/test/CodeGen/p0963r3.cpp
new file mode 100644
index 0000000000000..b48b5294e093e
--- /dev/null
+++ b/clang/test/CodeGen/p0963r3.cpp
@@ -0,0 +1,212 @@
+// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm -triple=x86_64-pc-linux-gnu %s -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++2c -verify -fsyntax-only -fexperimental-new-constant-interpreter -triple=x86_64-pc-linux-gnu %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 flag = false;
+
+ constexpr explicit operator bool() {
+ flag = true;
+ 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;
+ }
+};
+
+} // 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 {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// 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 {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// 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 {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}}
+
+ while (auto [i, j] = S(5, 6))
+ break;
+
+// CHECK: while.cond{{.*}}:
+// CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// 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 {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// 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 {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
+// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
+// 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 {{.*}} 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
diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp
index f4a4dc5375bdc..a6ca642269eee 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..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-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++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 {
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 3797654952319..64b731c19c517 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>
More information about the cfe-commits
mailing list