https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169688
>From 162cc15dff58d47852cc0d88311754395bec12f5 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
