[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 9: Control Flow) (PR #169688)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Jul 10 13:00:51 PDT 2026
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169688
>From 0a286daf8b71a66ef8c70005e891df468444c10c Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 26 Nov 2025 17:21:39 +0100
Subject: [PATCH 1/2] [Clang] [C++26] Expansion Statements (Part 9)
---
.../clang/Basic/DiagnosticSemaKinds.td | 6 +
clang/include/clang/Sema/ScopeInfo.h | 9 +-
clang/include/clang/Sema/Sema.h | 6 +-
clang/lib/Parse/ParseStmt.cpp | 12 +-
clang/lib/Sema/SemaLookup.cpp | 47 ++++--
clang/lib/Sema/SemaStmt.cpp | 30 +++-
.../cxx2c-expansion-stmts-control-flow.cpp | 135 ++++++++++++++++++
7 files changed, 228 insertions(+), 17 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5a553f92fb0e8..bfe0fe75389fa 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3746,6 +3746,12 @@ def err_expansion_stmt_incomplete : Error<
"cannot expand expression of incomplete type %0">;
def err_expansion_stmt_lambda : Error<
"cannot expand lambda closure type">;
+def err_expansion_stmt_case : Error<
+ "%select{'case'|'default'}0 belongs to 'switch' outside enclosing expansion statement">;
+def note_enclosing_switch_statement_here : Note<
+ "switch statement is here">;
+def err_expansion_stmt_label : Error<
+ "labels are not allowed in expansion statements">;
def err_attribute_patchable_function_entry_invalid_section
: Error<"section argument to 'patchable_function_entry' attribute is not "
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index f334f58ebd0a7..a0753fa333770 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -201,8 +201,13 @@ class FunctionScopeInfo {
public:
/// A SwitchStmt, along with a flag indicating if its list of case statements
- /// is incomplete (because we dropped an invalid one while parsing).
- using SwitchInfo = llvm::PointerIntPair<SwitchStmt*, 1, bool>;
+ /// is incomplete (because we dropped an invalid one while parsing), as well
+ /// as the DeclContext containing the statement.
+ struct SwitchInfo : llvm::PointerIntPair<SwitchStmt *, 1, bool> {
+ DeclContext *EnclosingDC;
+ SwitchInfo(SwitchStmt *Switch, DeclContext *DC)
+ : PointerIntPair(Switch, false), EnclosingDC(DC) {}
+ };
/// SwitchStack - This is the current set of active switch statements in the
/// block.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 050da05a2e82b..d41cf7cdbcc00 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9610,9 +9610,11 @@ class Sema final : public SemaBase {
/// LookupOrCreateLabel - Do a name lookup of a label with the specified name.
/// If GnuLabelLoc is a valid source location, then this is a definition
/// of an __label__ label name, otherwise it is a normal label definition
- /// or use.
+ /// or use. If IsLabelStmt is true, then this is the label of a
+ /// labeled-statement.
LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
- SourceLocation GnuLabelLoc = SourceLocation());
+ SourceLocation GnuLabelLoc = SourceLocation(),
+ bool IsLabelStmt = false);
/// Perform a name lookup for a label with the specified name; this does not
/// create a new label if the lookup fails.
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 756d194a767cf..32cab6782f9e5 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -738,8 +738,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
// identifier ':' statement
SourceLocation ColonLoc = ConsumeToken();
- LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
- IdentTok.getLocation());
+ LabelDecl *LD = Actions.LookupOrCreateLabel(
+ IdentTok.getIdentifierInfo(), IdentTok.getLocation(), /*GnuLabelLoc=*/{},
+ /*IsLabelStmt=*/true);
// Read label attributes, if present.
StmtResult SubStmt;
@@ -783,6 +784,13 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
+ // If a label cannot appear here, just return the underlying statement. We
+ // already diagnosed this as invalid in LookupOrCreateLabel() above.
+ if (!LD) {
+ Attrs.clear();
+ return SubStmt.get();
+ }
+
Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
Attrs.clear();
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 9502b440dbe97..ed07395c00e72 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4468,7 +4468,8 @@ LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
}
LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
- SourceLocation GnuLabelLoc) {
+ SourceLocation GnuLabelLoc,
+ bool IsLabelStmt) {
if (GnuLabelLoc.isValid()) {
// Local label definitions always shadow existing labels.
auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
@@ -4477,15 +4478,43 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
return cast<LabelDecl>(Res);
}
- // Not a GNU local label.
- LabelDecl *Res = LookupExistingLabel(II, Loc);
- if (!Res) {
- // If not forward referenced or defined already, create the backing decl.
- Res = LabelDecl::Create(Context, CurContext, Loc, II);
- Scope *S = CurScope->getFnParent();
- assert(S && "Not in a function?");
- PushOnScopeChains(Res, S, true);
+ LabelDecl *Existing = LookupExistingLabel(II, Loc);
+
+ // C++26 [stmt.label]p4 An identifier label shall not be enclosed by an
+ // expansion-statement.
+ //
+ // As an extension, we allow GNU local labels since they are logically
+ // scoped to the containing block, which prevents us from ending up with
+ // multiple copies of the same label in a function after instantiation.
+ //
+ // While allowing this is slightly more complicated, it also has the nice
+ // side-effect of avoiding otherwise rather horrible diagnostics you'd get
+ // when trying to use '__label__' if we didn't support this.
+ if (IsLabelStmt && CurContext->isExpansionStmt()) {
+ if (Existing && Existing->isGnuLocal())
+ return Existing;
+
+ // Drop the label from the AST as creating it anyway would cause us to
+ // either issue various unhelpful diagnostics (if we were to declare
+ // it in the function decl context) or shadow a valid label with the
+ // same name outside the expansion statement.
+ Diag(Loc, diag::err_expansion_stmt_label);
+ return nullptr;
}
+
+ if (Existing)
+ return Existing;
+
+ // Declare non-local labels outside any expansion statements; this is required
+ // to support jumping out of an expansion statement.
+ ContextRAII Ctx{*this, CurContext->getEnclosingNonExpansionStatementContext(),
+ /*NewThisContext=*/false};
+
+ // Not a GNU local label. Create the backing decl.
+ auto *Res = LabelDecl::Create(Context, CurContext, Loc, II);
+ Scope *S = CurScope->getFnParent();
+ assert(S && "Not in a function?");
+ PushOnScopeChains(Res, S, true);
return Res;
}
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index d53e355fbec70..406a6da0903fb 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -528,6 +528,25 @@ Sema::ActOnCaseExpr(SourceLocation CaseLoc, ExprResult Val) {
return CheckAndFinish(Val.get());
}
+static bool DiagnoseSwitchCaseInExpansionStmt(Sema &S, SourceLocation KwLoc,
+ bool IsDefault) {
+ // C++26 [stmt.expand] The compound-statement of an expansion-statement is a
+ // control-flow-limited statement.
+ //
+ // We diagnose this here rather than in JumpDiagnostics because those run
+ // after the expansion statement is instantiated, at which point we will have
+ // have already complained about duplicate case labels, which is not exactly
+ // great QOI.
+ if (S.CurContext->isExpansionStmt() &&
+ S.getCurFunction()->SwitchStack.back().EnclosingDC != S.CurContext) {
+ S.Diag(KwLoc, diag::err_expansion_stmt_case) << IsDefault;
+ S.Diag(S.getCurFunction()->SwitchStack.back().getPointer()->getSwitchLoc(),
+ diag::note_enclosing_switch_statement_here);
+ return true;
+ }
+ return false;
+}
+
StmtResult
Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
SourceLocation DotDotDotLoc, ExprResult RHSVal,
@@ -547,6 +566,9 @@ Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
return StmtError();
}
+ if (DiagnoseSwitchCaseInExpansionStmt(*this, CaseLoc, false))
+ return StmtError();
+
if (LangOpts.OpenACC &&
getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
Diag(CaseLoc, diag::err_acc_branch_in_out_compute_construct)
@@ -572,6 +594,9 @@ Sema::ActOnDefaultStmt(SourceLocation DefaultLoc, SourceLocation ColonLoc,
return SubStmt;
}
+ if (DiagnoseSwitchCaseInExpansionStmt(*this, DefaultLoc, true))
+ return StmtError();
+
if (LangOpts.OpenACC &&
getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
Diag(DefaultLoc, diag::err_acc_branch_in_out_compute_construct)
@@ -1196,8 +1221,9 @@ StmtResult Sema::ActOnStartOfSwitchStmt(SourceLocation SwitchLoc,
auto *SS = SwitchStmt::Create(Context, InitStmt, Cond.get().first, CondExpr,
LParenLoc, RParenLoc);
+ SS->setSwitchLoc(SwitchLoc);
getCurFunction()->SwitchStack.push_back(
- FunctionScopeInfo::SwitchInfo(SS, false));
+ FunctionScopeInfo::SwitchInfo(SS, CurContext));
return SS;
}
@@ -1313,7 +1339,7 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, Stmt *Switch,
BodyStmt = new (Context) NullStmt(BodyStmt->getBeginLoc());
}
- SS->setBody(BodyStmt, SwitchLoc);
+ SS->setBody(BodyStmt);
Expr *CondExpr = SS->getCond();
if (!CondExpr) return StmtError();
diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp
new file mode 100644
index 0000000000000..51b383d63cdcb
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp
@@ -0,0 +1,135 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fblocks -verify
+
+void g(int);
+
+void label() {
+ template for (auto x : {1, 2}) {
+ invalid1:; // expected-error {{labels are not allowed in expansion statements}}
+ invalid2:; // expected-error {{labels are not allowed in expansion statements}}
+ goto invalid1; // expected-error {{use of undeclared label 'invalid1'}}
+ }
+
+ template for (auto x : {1, 2}) {
+ (void) [] {
+ template for (auto x : {1, 2}) {
+ invalid3:; // expected-error {{labels are not allowed in expansion statements}}
+ }
+ ok:;
+ };
+
+ (void) ^{
+ template for (auto x : {1, 2}) {
+ invalid4:; // expected-error {{labels are not allowed in expansion statements}}
+ }
+ ok:;
+ };
+
+ struct X {
+ void f() {
+ ok:;
+ }
+ };
+ }
+
+ // GNU local labels are allowed.
+ template for (auto x : {1, 2}) {
+ __label__ a;
+ if (x == 1) goto a;
+ a:;
+ if (x == 1) goto a;
+ }
+
+ // Likewise, jumping *out* of an expansion statement is fine.
+ template for (auto x : {1, 2}) {
+ if (x == 1) goto lbl;
+ g(x);
+ }
+ lbl:;
+ template for (auto x : {1, 2}) {
+ if (x == 1) goto lbl;
+ g(x);
+ }
+
+ // Jumping into one is not possible, as local labels aren't visible
+ // outside the block that declares them, and non-local labels are invalid.
+ goto exp1; // expected-error {{use of undeclared label 'exp1'}}
+ goto exp3; // expected-error {{use of undeclared label 'exp3'}}
+ template for (auto x : {1, 2}) {
+ __label__ exp1, exp2;
+ exp1:;
+ exp2:;
+ exp3:; // expected-error {{labels are not allowed in expansion statements}}
+ }
+ goto exp2; // expected-error {{use of undeclared label 'exp2'}}
+
+ // Allow jumping from inside an expansion statement to a local label in
+ // one of its parents.
+ out1:;
+ template for (auto x : {1, 2}) {
+ __label__ x, y;
+ x:
+ goto out1;
+ goto out2;
+ template for (auto x : {3, 4}) {
+ goto x;
+ goto y;
+ goto out1;
+ goto out2;
+ }
+ y:
+ }
+ out2:;
+}
+
+
+void case_default(int i) {
+ switch (i) { // expected-note 3 {{switch statement is here}}
+ template for (auto x : {1, 2}) {
+ case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+ template for (auto x : {1, 2}) {
+ case 2:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+ }
+ default: // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
+ switch (i) { // expected-note {{switch statement is here}}
+ case 3:;
+ default:
+ template for (auto x : {1, 2}) {
+ case 4:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+ }
+ }
+ }
+ }
+
+ template for (auto x : {1, 2}) {
+ switch (i) {
+ case 1:;
+ default:
+ }
+ }
+
+ // Ensure that we diagnose this even if the statements would be discarded.
+ switch (i) { // expected-note 2 {{switch statement is here}}
+ template for (auto x : {}) {
+ case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+ default:; // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
+ }
+ }
+}
+
+void case_constexpr(int i) {
+ template for (constexpr auto x : {1, 2, 3}) { // expected-note {{in instantiation of expansion statement requested here}}
+ switch (i) {
+ case x:; // expected-note {{previous case defined here}}
+ case 2:; // expected-error {{duplicate case value: 'x' and '2' both equal '2'}}
+ default:;
+ }
+ }
+
+ template for (constexpr auto x : {1, 2, 3}) {
+ switch (i) {
+ case x:;
+ case 4:;
+ default:;
+ }
+ }
+}
>From 5665e7bc511d0fb16b15bf45dbde028a4f4d0575 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Fri, 10 Jul 2026 22:00:39 +0200
Subject: [PATCH 2/2] [Clang] [C++26] Expansion Statements (Part 10: Expansion
Limit) (#169689)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This implements a limit on the maximum number of expansions; this isn’t
in the standard (there is nothing on this in [implimits]), but my
reasoning is that if someone makes a mistake and inadvertently causes
e.g. 1 000 000 000 instantiations, Clang is going to hang... basically
for ever (for comparison, 1 million instantiations takes about 3 seconds
provided the body of the expansion statement is empty).
For now, I’ve set the limit to `256`, but that’s probably too low. I
don’t have strong opinions on the exact value; I definitely *do* think
that there should be *a* limit though. We *could* in theory reuse the
constexpr-steps value, but it feels a bit weird because this isn’t
really doing constant evaluation so much as just template instantiation.
---
clang/docs/ReleaseNotes.rst | 2 +
.../clang/Basic/DiagnosticCommonKinds.td | 4 -
clang/test/AST/ast-dump-expansion-stmt.cpp | 54 +++++++++
clang/test/AST/ast-print-expansion-stmts.cpp | 113 ++++++++++++++++++
clang/www/cxx_status.html | 8 +-
5 files changed, 176 insertions(+), 5 deletions(-)
create mode 100644 clang/test/AST/ast-dump-expansion-stmt.cpp
create mode 100644 clang/test/AST/ast-print-expansion-stmts.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fc17eead02589..1fab855bb1fc2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -126,6 +126,8 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Clang now has partial support for `P1306R5 <https://wg21.link/P1306R5>`_ Expansion Statements. Iterating expansion
+ statements currently cannot be expanded and will result in a diagnostic, but other types of expansion statements work.
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index f1ee130eb1f11..cb267e3ee05c1 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -22,10 +22,6 @@ def select_constexpr_spec_kind : TextSubstitution<
def fatal_too_many_errors
: Error<"too many errors emitted, stopping now">, DefaultFatal;
-// TODO: Remove this.
-def err_expansion_statements_todo : Error<
- "TODO (expansion statements)">;
-
def warn_stack_exhausted : Warning<
"stack nearly exhausted; compilation time may suffer, and "
"crashes due to stack overflow are likely">,
diff --git a/clang/test/AST/ast-dump-expansion-stmt.cpp b/clang/test/AST/ast-dump-expansion-stmt.cpp
new file mode 100644
index 0000000000000..94f0ee1449f17
--- /dev/null
+++ b/clang/test/AST/ast-dump-expansion-stmt.cpp
@@ -0,0 +1,54 @@
+// Test without serialization:
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -ast-dump %s
+//
+// Test with serialization:
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -emit-pch -o %t %s
+// RUN: %clang_cc1 -x c++ -std=c++26 -triple x86_64-unknown-unknown -include-pch %t -ast-dump-all /dev/null \
+// RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//"
+
+#if 0 // Disabled until we support iterating expansion statements.
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+ T data[size]{};
+ constexpr const T* begin() const { return data; }
+ constexpr const T* end() const { return data + size; }
+};
+#endif // 0
+
+void foo(int);
+
+template <typename T>
+void test(T t) {
+ // CHECK: CXXExpansionStmtDecl
+ // CHECK-NEXT: CXXExpansionStmtPattern {{.*}} enumerating
+ // CHECK: CXXExpansionStmtInstantiation
+ template for (auto x : {1, 2, 3}) {
+ foo(x);
+ }
+
+#if 0 // Disabled until we support iterating expansion statements.
+ // NOTE: Remove 'DISABLED-' when the '#if 0' is removed.
+ // DISABLED-CHECK: CXXExpansionStmtDecl
+ // DISABLED-CHECK-NEXT: CXXExpansionStmtPattern {{.*}} iterating
+ // DISABLED-CHECK: CXXExpansionStmtInstantiation
+ static constexpr Array<int, 3> a;
+ template for (auto x : a) {
+ foo(x);
+ }
+#endif
+
+ // CHECK: CXXExpansionStmtDecl
+ // CHECK-NEXT: CXXExpansionStmtPattern {{.*}} destructuring
+ // CHECK: CXXExpansionStmtInstantiation
+ int arr[3]{1, 2, 3};
+ template for (auto x : arr) {
+ foo(x);
+ }
+
+ // CHECK: CXXExpansionStmtDecl
+ // CHECK-NEXT: CXXExpansionStmtPattern {{.*}} dependent
+ // CHECK-NOT: CXXExpansionStmtInstantiation
+ template for (auto x : t) {
+ foo(x);
+ }
+}
diff --git a/clang/test/AST/ast-print-expansion-stmts.cpp b/clang/test/AST/ast-print-expansion-stmts.cpp
new file mode 100644
index 0000000000000..880e17a9d3e2f
--- /dev/null
+++ b/clang/test/AST/ast-print-expansion-stmts.cpp
@@ -0,0 +1,113 @@
+// Without serialization:
+// RUN: %clang_cc1 -std=c++26 -ast-print %s | FileCheck %s
+//
+// With serialization:
+// RUN: %clang_cc1 -std=c++26 -emit-pch -o %t %s
+// RUN: %clang_cc1 -x c++ -std=c++26 -include-pch %t -ast-print /dev/null | FileCheck %s
+
+#if 0 // Disabled until we support iterating expansion statements.
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+ T data[size]{};
+ constexpr const T* begin() const { return data; }
+ constexpr const T* end() const { return data + size; }
+};
+#endif // 0
+
+// CHECK: void foo(int);
+void foo(int);
+
+// CHECK: template <typename T> void test(T t) {
+template <typename T>
+void test(T t) {
+ // Enumerating expansion statement.
+ //
+ // CHECK: template for (auto x : {1, 2, 3}) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ template for (auto x : {1, 2, 3}) {
+ foo(x);
+ }
+
+#if 0 // Disabled until we support iterating expansion statements.
+ // Iterating expansion statement.
+ //
+ // NOTE: Remove 'DISABLED-' when the '#if 0' is removed.
+ // DISABLED-CHECK: static constexpr Array<int, 3> a;
+ // DISABLED-CHECK-NEXT: template for (auto x : (a)) {
+ // DISABLED-CHECK-NEXT: foo(x);
+ // DISABLED-CHECK-NEXT: }
+ static constexpr Array<int, 3> a;
+ template for (auto x : a) {
+ foo(x);
+ }
+#endif // 0
+
+ // Destructuring expansion statement.
+ //
+ // CHECK: int arr[3]{1, 2, 3};
+ // CHECK-NEXT: template for (auto x : arr) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ int arr[3]{1, 2, 3};
+ template for (auto x : arr) {
+ foo(x);
+ }
+
+ // Dependent expansion statement.
+ //
+ // CHECK: template for (auto x : t) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ template for (auto x : t) {
+ foo(x);
+ }
+}
+
+// CHECK: template <typename T> void test2(T t) {
+template <typename T>
+void test2(T t) {
+ // Enumerating expansion statement.
+ //
+ // CHECK: template for (int x : {1, 2, 3}) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ template for (int x : {1, 2, 3}) {
+ foo(x);
+ }
+
+#if 0 // Disabled until we support iterating expansion statements.
+ // Iterating expansion statement.
+ //
+ // NOTE: Remove 'DISABLED-' when the '#if 0' is removed.
+ // DISABLED-CHECK: static constexpr Array<int, 3> a;
+ // DISABLED-CHECK-NEXT: template for (int x : (a)) {
+ // DISABLED-CHECK-NEXT: foo(x);
+ // DISABLED-CHECK-NEXT: }
+
+ static constexpr Array<int, 3> a;
+ template for (int x : a) {
+ foo(x);
+ }
+#endif // 0
+
+ // Destructuring expansion statement.
+ //
+ // CHECK: int arr[3]{1, 2, 3};
+ // CHECK-NEXT: template for (int x : arr) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ int arr[3]{1, 2, 3};
+ template for (int x : arr) {
+ foo(x);
+ }
+
+ // Dependent expansion statement.
+ //
+ // CHECK: template for (int x : t) {
+ // CHECK-NEXT: foo(x);
+ // CHECK-NEXT: }
+ template for (int x : t) {
+ foo(x);
+ }
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2c834b07f9a8f..9ff43c713d5b3 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -320,7 +320,13 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Expansion Statements</td>
<td><a href="https://wg21.link/P1306">P1306R5</a></td>
- <td class="none" align="center">No</td>
+ <td class="partial" align="center">
+ <details>
+ <summary>Clang 23 (Partial)</summary>
+ Iterating expansion statements currently cannot be expanded and will
+ result in a diagnostic, but other types of expansion statements work.
+ </details>
+ </td>
</tr>
<tr>
<td>constexpr virtual inheritance</td>
More information about the llvm-branch-commits
mailing list