Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
56a2eff
Parsing labeled break/continue
Sirraide Aug 9, 2025
746b1ab
Rename getBreak/ContinueLoc
Sirraide Aug 9, 2025
27a2eae
more renaming
Sirraide Aug 9, 2025
674bcf4
Sema
Sirraide Aug 9, 2025
a2e7bc2
Remove unused variable
Sirraide Aug 9, 2025
42b1041
CodeGen
Sirraide Aug 9, 2025
5a0965e
Add compat diags
Sirraide Aug 9, 2025
76d56e1
Basic C++ support
Sirraide Aug 9, 2025
1b39ff4
ObjC support
Sirraide Aug 9, 2025
0847467
Constexpr support
Sirraide Aug 9, 2025
6301ef5
Template support
Sirraide Aug 9, 2025
e05b8b1
Diagnose invalid break/continue in OpenACC/OpenMP
Sirraide Aug 9, 2025
db17a67
Update various AST dumpers
Sirraide Aug 9, 2025
25fb4a3
clang-format
Sirraide Aug 9, 2025
7e41861
update grammar comment
Sirraide Aug 9, 2025
1f515b4
Add release note
Sirraide Aug 9, 2025
f8374d3
Update docs
Sirraide Aug 9, 2025
aaa117c
remove comment
Sirraide Aug 9, 2025
8b6feac
add another test case
Sirraide Aug 9, 2025
f33869c
Move checks out of jump checker
Sirraide Aug 14, 2025
da3b554
Support multiple labels
Sirraide Aug 14, 2025
093fb04
clang-format
Sirraide Aug 14, 2025
c9de06b
Add a cc1 option and disable the feature in every language mode other…
Sirraide Aug 14, 2025
8ac7910
Update docs
Sirraide Aug 14, 2025
9377a80
Address review comments
Sirraide Aug 20, 2025
8fad2cb
Some more minor fixes
Sirraide Aug 20, 2025
1a15401
[analyzer] Add test for labeled break/continue N3355
steakhal Aug 22, 2025
5462978
Address review comments
Sirraide Aug 22, 2025
b2abfa5
Only track a single preceding label
Sirraide Aug 22, 2025
1fe9d0d
clang-format
Sirraide Aug 22, 2025
537b35f
address review comments
Sirraide Sep 2, 2025
f6d9e5d
clang-format
Sirraide Sep 2, 2025
1f2dde5
reverse constructor delegation
Sirraide Sep 2, 2025
c9b8688
Update the compat diag to say ‘named’ instead of ‘labeled’
Sirraide Sep 2, 2025
bc1f635
‘labeled’ -> ‘named’ in a number of places
Sirraide Sep 2, 2025
35bc625
Fix typo in comment
Sirraide Sep 2, 2025
797bf22
add missing const
Sirraide Sep 2, 2025
ece369b
rename Label -> LabelLoc
Sirraide Sep 2, 2025
1c503df
Merge branch 'main' into n3355
Sirraide Sep 2, 2025
c6d433d
fix return type
Sirraide Sep 2, 2025
82f783d
getNamedLoopOrSwitch() should return nullptr if there is no label
Sirraide Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1106,11 +1106,11 @@ class FindControlFlow : public RecursiveASTVisitor<FindControlFlow> {
return true;
}
bool VisitBreakStmt(BreakStmt *B) {
found(Break, B->getBreakLoc());
found(Break, B->getKwLoc());
return true;
}
bool VisitContinueStmt(ContinueStmt *C) {
found(Continue, C->getContinueLoc());
found(Continue, C->getKwLoc());
return true;
}
bool VisitSwitchCase(SwitchCase *C) {
Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ C Language Changes

C2y Feature Support
^^^^^^^^^^^^^^^^^^^
- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops.

C23 Feature Support
^^^^^^^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/JSONNodeDumper.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class JSONNodeDumper
void VisitStringLiteral(const StringLiteral *SL);
void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *BLE);

void VisitLoopControlStmt(const LoopControlStmt *LS);
void VisitIfStmt(const IfStmt *IS);
void VisitSwitchStmt(const SwitchStmt *SS);
void VisitCaseStmt(const CaseStmt *CS);
Expand Down
129 changes: 80 additions & 49 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,24 +277,14 @@ class alignas(void *) Stmt {
SourceLocation GotoLoc;
};

class ContinueStmtBitfields {
friend class ContinueStmt;
class LoopControlStmtBitfields {
friend class LoopControlStmt;

LLVM_PREFERRED_TYPE(StmtBitfields)
unsigned : NumStmtBits;

/// The location of the "continue".
SourceLocation ContinueLoc;
};

class BreakStmtBitfields {
friend class BreakStmt;

LLVM_PREFERRED_TYPE(StmtBitfields)
unsigned : NumStmtBits;

/// The location of the "break".
SourceLocation BreakLoc;
/// The location of the "continue"/"break".
SourceLocation KwLoc;
};

class ReturnStmtBitfields {
Expand Down Expand Up @@ -1325,8 +1315,7 @@ class alignas(void *) Stmt {
DoStmtBitfields DoStmtBits;
ForStmtBitfields ForStmtBits;
GotoStmtBitfields GotoStmtBits;
ContinueStmtBitfields ContinueStmtBits;
BreakStmtBitfields BreakStmtBits;
LoopControlStmtBitfields LoopControlStmtBits;
ReturnStmtBitfields ReturnStmtBits;
SwitchCaseBitfields SwitchCaseBits;

Expand Down Expand Up @@ -2184,6 +2173,14 @@ class LabelStmt : public ValueStmt {
SourceLocation getBeginLoc() const { return getIdentLoc(); }
SourceLocation getEndLoc() const LLVM_READONLY { return SubStmt->getEndLoc();}

/// Look through nested labels and return the first non-label statement; e.g.
/// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop.
const Stmt *getInnermostLabeledStmt() const;
Stmt *getInnermostLabeledStmt() {
return const_cast<Stmt *>(
const_cast<const LabelStmt *>(this)->getInnermostLabeledStmt());
}

child_range children() { return child_range(&SubStmt, &SubStmt + 1); }

const_child_range children() const {
Expand Down Expand Up @@ -3056,26 +3053,53 @@ class IndirectGotoStmt : public Stmt {
}
};

/// ContinueStmt - This represents a continue.
class ContinueStmt : public Stmt {
public:
ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) {
setContinueLoc(CL);
/// Base class for BreakStmt and ContinueStmt.
class LoopControlStmt : public Stmt {
/// If this is a named break/continue, the label whose statement we're
/// targeting, as well as the source location of the label after the
/// keyword; for example:
///
/// a: // <-- TargetLabel
/// for (;;)
/// break a; // <-- Label
///
LabelDecl *TargetLabel = nullptr;
SourceLocation Label;

protected:
LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc,
LabelDecl *Target)
: Stmt(Class), TargetLabel(Target), Label(LabelLoc) {
setKwLoc(Loc);
}

/// Build an empty continue statement.
explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
LoopControlStmt(StmtClass Class, SourceLocation Loc)
: LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {}

SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}

SourceLocation getBeginLoc() const { return getContinueLoc(); }
SourceLocation getEndLoc() const { return getContinueLoc(); }
public:
SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }

static bool classof(const Stmt *T) {
return T->getStmtClass() == ContinueStmtClass;
SourceLocation getBeginLoc() const { return getKwLoc(); }
SourceLocation getEndLoc() const {
return hasLabelTarget() ? getLabelLoc() : getKwLoc();
}

bool hasLabelTarget() const { return TargetLabel != nullptr; }

SourceLocation getLabelLoc() const { return Label; }
void setLabelLoc(SourceLocation L) { Label = L; }

LabelDecl *getLabelDecl() { return TargetLabel; }
const LabelDecl *getLabelDecl() const { return TargetLabel; }
void setLabelDecl(LabelDecl *S) { TargetLabel = S; }

/// If this is a named break/continue, get the loop or switch statement
/// that this targets.
const Stmt *getNamedLoopOrSwitch() const;

// Iterators
child_range children() {
return child_range(child_iterator(), child_iterator());
Expand All @@ -3084,35 +3108,42 @@ class ContinueStmt : public Stmt {
const_child_range children() const {
return const_child_range(const_child_iterator(), const_child_iterator());
}
};

/// BreakStmt - This represents a break.
class BreakStmt : public Stmt {
public:
BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
setBreakLoc(BL);
static bool classof(const Stmt *T) {
StmtClass Class = T->getStmtClass();
return Class == ContinueStmtClass || Class == BreakStmtClass;
}
};

/// Build an empty break statement.
explicit BreakStmt(EmptyShell Empty) : Stmt(BreakStmtClass, Empty) {}

SourceLocation getBreakLoc() const { return BreakStmtBits.BreakLoc; }
void setBreakLoc(SourceLocation L) { BreakStmtBits.BreakLoc = L; }
/// ContinueStmt - This represents a continue.
class ContinueStmt : public LoopControlStmt {
public:
ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
: LoopControlStmt(ContinueStmtClass, CL, LabelLoc, Target) {}

SourceLocation getBeginLoc() const { return getBreakLoc(); }
SourceLocation getEndLoc() const { return getBreakLoc(); }
/// Build an empty continue statement.
explicit ContinueStmt(EmptyShell Empty)
: LoopControlStmt(ContinueStmtClass, Empty) {}

static bool classof(const Stmt *T) {
return T->getStmtClass() == BreakStmtClass;
return T->getStmtClass() == ContinueStmtClass;
}
};

// Iterators
child_range children() {
return child_range(child_iterator(), child_iterator());
}
/// BreakStmt - This represents a break.
class BreakStmt : public LoopControlStmt {
public:
BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
: LoopControlStmt(BreakStmtClass, CL, LabelLoc, Target) {}

const_child_range children() const {
return const_child_range(const_child_iterator(), const_child_iterator());
/// Build an empty break statement.
explicit BreakStmt(EmptyShell Empty)
: LoopControlStmt(BreakStmtClass, Empty) {}

static bool classof(const Stmt *T) {
return T->getStmtClass() == BreakStmtClass;
}
};

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/TextNodeDumper.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class TextNodeDumper
void VisitExpressionTemplateArgument(const TemplateArgument &TA);
void VisitPackTemplateArgument(const TemplateArgument &TA);

void VisitLoopControlStmt(const LoopControlStmt *L);
void VisitIfStmt(const IfStmt *Node);
void VisitSwitchStmt(const SwitchStmt *Node);
void VisitWhileStmt(const WhileStmt *Node);
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ def warn_c23_compat_case_range : Warning<
DefaultIgnore, InGroup<CPre2yCompat>;
def ext_c2y_case_range : Extension<
"case ranges are a C2y extension">, InGroup<C2y>;
def err_c2y_labeled_break_continue : Error<
"named %select{'break'|'continue'}0 is only supported in C2y">;

// Generic errors.
def err_expected_expression : Error<"expected expression">;
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10796,6 +10796,11 @@ def err_continue_not_in_loop : Error<
"'continue' statement not in loop statement">;
def err_break_not_in_loop_or_switch : Error<
"'break' statement not in loop or switch statement">;
def err_break_continue_label_not_found : Error<
"'%select{break|continue}0' label does not name an enclosing "
"%select{loop or 'switch'|loop}0">;
def err_continue_switch : Error<
"label of 'continue' refers to a switch statement">;
def warn_loop_ctrl_binds_to_inner : Warning<
"'%0' is bound to current loop, GCC binds it to the enclosing loop">,
InGroup<GccCompat>;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ LANGOPT(NoHonorInfs , 1, 0, Benign, "Permit Floating Point optimization wi
LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization without regard to signed zeros")
LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal")
LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation")
LANGOPT(NamedLoops , 1, 0, Benign, "Permit named break/continue")

ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.")

Expand Down
7 changes: 5 additions & 2 deletions clang/include/clang/Basic/StmtNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ def DoStmt : StmtNode<Stmt>;
def ForStmt : StmtNode<Stmt>;
def GotoStmt : StmtNode<Stmt>;
def IndirectGotoStmt : StmtNode<Stmt>;
def ContinueStmt : StmtNode<Stmt>;
def BreakStmt : StmtNode<Stmt>;
def ReturnStmt : StmtNode<Stmt>;
def DeclStmt : StmtNode<Stmt>;
def SwitchCase : StmtNode<Stmt, 1>;
Expand All @@ -26,6 +24,11 @@ def DefaultStmt : StmtNode<SwitchCase>;
def CapturedStmt : StmtNode<Stmt>;
def SYCLKernelCallStmt : StmtNode<Stmt>;

// Break/continue.
def LoopControlStmt : StmtNode<Stmt, 1>;
def ContinueStmt : StmtNode<LoopControlStmt>;
def BreakStmt : StmtNode<LoopControlStmt>;

// Statements that might produce a value (for example, as the last non-null
// statement in a GNU statement-expression).
def ValueStmt : StmtNode<Stmt, 1>;
Expand Down
11 changes: 11 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,17 @@ defm auto_import : BoolFOption<"auto-import",
def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias<offload_targets_EQ>,
HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">;

// This flag is only here so we can test the named loops implementation
// in C++ mode and C language modes before C2y to make sure it actually
// works; it should be removed once the syntax of the feature is stable
// enough to backport it to earlier language modes (and to C++ if it ever
// gets standardised as a C++ feature).
defm named_loops
: BoolFOption<
"named-loops", LangOpts<"NamedLoops">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Enable support for named loops">,
NegFlag<SetFalse>>;

// C++ Coroutines
defm coroutines : BoolFOption<"coroutines",
LangOpts<"Coroutines">, Default<cpp20.KeyPath>,
Expand Down
26 changes: 18 additions & 8 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7213,7 +7213,8 @@ class Parser : public CodeCompletionHandler {
/// 'while', or 'for').
StmtResult
ParseStatement(SourceLocation *TrailingElseLoc = nullptr,
ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt);
ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt,
LabelDecl *PrecedingLabel = nullptr);

/// ParseStatementOrDeclaration - Read 'statement' or 'declaration'.
/// \verbatim
Expand Down Expand Up @@ -7268,12 +7269,13 @@ class Parser : public CodeCompletionHandler {
///
StmtResult
ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc = nullptr);
SourceLocation *TrailingElseLoc = nullptr,
LabelDecl *PrecedingLabel = nullptr);

StmtResult ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs,
ParsedAttributes &DeclSpecAttrs);
ParsedAttributes &DeclSpecAttrs, LabelDecl *PrecedingLabel);

/// Parse an expression statement.
StmtResult ParseExprStatement(ParsedStmtContext StmtCtx);
Expand Down Expand Up @@ -7398,23 +7400,25 @@ class Parser : public CodeCompletionHandler {
/// 'switch' '(' expression ')' statement
/// [C++] 'switch' '(' condition ')' statement
/// \endverbatim
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc);
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc,
LabelDecl *PrecedingLabel);

/// ParseWhileStatement
/// \verbatim
/// while-statement: [C99 6.8.5.1]
/// 'while' '(' expression ')' statement
/// [C++] 'while' '(' condition ')' statement
/// \endverbatim
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc);
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc,
LabelDecl *PrecedingLabel);

/// ParseDoStatement
/// \verbatim
/// do-statement: [C99 6.8.5.2]
/// 'do' statement 'while' '(' expression ')' ';'
/// \endverbatim
/// Note: this lets the caller parse the end ';'.
StmtResult ParseDoStatement();
StmtResult ParseDoStatement(LabelDecl *PrecedingLabel);

/// ParseForStatement
/// \verbatim
Expand All @@ -7441,7 +7445,8 @@ class Parser : public CodeCompletionHandler {
/// [C++0x] expression
/// [C++0x] braced-init-list [TODO]
/// \endverbatim
StmtResult ParseForStatement(SourceLocation *TrailingElseLoc);
StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
LabelDecl *PrecedingLabel);

/// ParseGotoStatement
/// \verbatim
Expand All @@ -7458,6 +7463,7 @@ class Parser : public CodeCompletionHandler {
/// \verbatim
/// jump-statement:
/// 'continue' ';'
/// [C2y] 'continue' identifier ';'
/// \endverbatim
///
/// Note: this lets the caller parse the end ';'.
Expand All @@ -7468,6 +7474,7 @@ class Parser : public CodeCompletionHandler {
/// \verbatim
/// jump-statement:
/// 'break' ';'
/// [C2y] 'break' identifier ';'
/// \endverbatim
///
/// Note: this lets the caller parse the end ';'.
Expand All @@ -7484,9 +7491,12 @@ class Parser : public CodeCompletionHandler {
/// \endverbatim
StmtResult ParseReturnStatement();

StmtResult ParseBreakOrContinueStatement(bool IsContinue);

StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
ParsedAttributes &Attrs);
ParsedAttributes &Attrs,
LabelDecl *PrecedingLabel);

void ParseMicrosoftIfExistsStatement(StmtVector &Stmts);

Expand Down
Loading
Loading