https://github.com/Sirraide created 
https://github.com/llvm/llvm-project/pull/169688

None

>From 00062fb9c201c25bd6a3b951d47e9e2a6efd62de Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 26 Nov 2025 17:21:39 +0100
Subject: [PATCH] [Clang] [C++26] Expansion Statements (Part 9)

---
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/include/clang/Sema/ScopeInfo.h          |   6 +-
 clang/include/clang/Sema/Sema.h               |   3 +-
 clang/lib/Parse/ParseStmt.cpp                 |  11 +-
 clang/lib/Sema/SemaLookup.cpp                 |  47 +++++--
 clang/lib/Sema/SemaStmt.cpp                   |  30 ++++-
 .../cxx2c-expansion-stmts-control-flow.cpp    | 117 ++++++++++++++++++
 7 files changed, 205 insertions(+), 15 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 0ddaa461deff5..5115c175849e1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3706,6 +3706,12 @@ def err_expansion_stmt_invalid_init : Error<
   "cannot expand expression of 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 4f4d38c961140..2a410bd2eab91 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -202,7 +202,11 @@ 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>;
+  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 b102544342416..82fc2875e2abf 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9519,7 +9519,8 @@ class Sema final : public SemaBase {
   /// of an __label__ label name, otherwise it is a normal label definition
   /// or use.
   LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
-                                 SourceLocation GnuLabelLoc = 
SourceLocation());
+                                 SourceLocation GnuLabelLoc = SourceLocation(),
+                                 bool ForLabelStmt = 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 39751c79c6852..7b0e0ff17733b 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -715,8 +715,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=*/{},
+      /*ForLabelStmt=*/true);
 
   // Read label attributes, if present.
   StmtResult SubStmt;
@@ -760,6 +761,12 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes 
&Attrs,
 
   DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
 
+  // If a label cannot appear here, just return the underlying statement.
+  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 88dcd27d45ad2..576ec6c80770e 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4463,7 +4463,8 @@ LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, 
SourceLocation Loc) {
 }
 
 LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
-                                     SourceLocation GnuLabelLoc) {
+                                     SourceLocation GnuLabelLoc,
+                                     bool ForLabelStmt) {
   if (GnuLabelLoc.isValid()) {
     // Local label definitions always shadow existing labels.
     auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
@@ -4472,15 +4473,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 (ForLabelStmt && 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 47c8f9ab6725c..78114fa097f16 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..83a87f74a6e1d
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp
@@ -0,0 +1,117 @@
+// 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}}
+    }
+  }
+}

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to