Skip to content

[Clang] [C2y] Implement N3355 ‘Named Loops’ #152870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,7 @@ Attributes (N2335) C
``#embed`` (N3017) C23 C89, C++
Octal literals prefixed with ``0o`` or ``0O`` C2y C89, C++
``_Countof`` (N3369, N3469) C2y C89
Named Loops (N3355) C2y C89, C++
============================================= ================================ ============= =============

Builtin type aliases
Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ C Language Changes

C2y Feature Support
^^^^^^^^^^^^^^^^^^^
- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops. This feature
is also available in earlier language modes and in C++ as an extension.

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
113 changes: 66 additions & 47 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 @@ -3056,25 +3045,42 @@ class IndirectGotoStmt : public Stmt {
}
};

/// ContinueStmt - This represents a continue.
class ContinueStmt : public Stmt {
/// Base class for BreakStmt and ContinueStmt.
class LoopControlStmt : public Stmt {
/// If this is a labeled break/continue, the label whose statement we're
/// targeting.
LabelDecl *TargetLabel = nullptr;

/// Location of the label, if any.
SourceLocation Label;

protected:
LoopControlStmt(StmtClass Class, SourceLocation Loc) : Stmt(Class) {
setKwLoc(Loc);
}

LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}

public:
ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) {
setContinueLoc(CL);
SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }

SourceLocation getBeginLoc() const { return getKwLoc(); }
SourceLocation getEndLoc() const {
return isLabeled() ? getLabelLoc() : getKwLoc();
}

/// Build an empty continue statement.
explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
bool isLabeled() const { return TargetLabel; }

SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
SourceLocation getLabelLoc() const { return Label; }
void setLabelLoc(SourceLocation L) { Label = L; }

SourceLocation getBeginLoc() const { return getContinueLoc(); }
SourceLocation getEndLoc() const { return getContinueLoc(); }
LabelDecl *getLabelDecl() const { return TargetLabel; }
void setLabelDecl(LabelDecl *S) { TargetLabel = S; }

static bool classof(const Stmt *T) {
return T->getStmtClass() == ContinueStmtClass;
}
/// If this is a labeled break/continue, get the loop or switch statement
/// that this targets.
Stmt *getLabelTarget() const;

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

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

/// BreakStmt - This represents a break.
class BreakStmt : public Stmt {
/// ContinueStmt - This represents a continue.
class ContinueStmt : public LoopControlStmt {
public:
BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
setBreakLoc(BL);
ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
: LoopControlStmt(ContinueStmtClass, CL) {
setLabelLoc(LabelLoc);
setLabelDecl(Target);
}

/// 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; }

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) {
setLabelLoc(LabelLoc);
setLabelDecl(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
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ 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 warn_c2y_labeled_break_continue
: Warning<"labeled %select{'break'|'continue'}0 is incompatible with C "
"standards before C2y">,
DefaultIgnore,
InGroup<CPre2yCompat>;
def ext_c2y_labeled_break_continue
: Extension<"labeled %select{'break'|'continue'}0 is a C2y extension">,
InGroup<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{continue|break}0' label does not name an enclosing "
"%select{loop|loop or 'switch'}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
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
4 changes: 4 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7458,6 +7458,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 +7469,7 @@ class Parser : public CodeCompletionHandler {
/// \verbatim
/// jump-statement:
/// 'break' ';'
/// [C2y] 'break' identifier ';'
/// \endverbatim
///
/// Note: this lets the caller parse the end ';'.
Expand All @@ -7484,6 +7486,8 @@ class Parser : public CodeCompletionHandler {
/// \endverbatim
StmtResult ParseReturnStatement();

StmtResult ParseBreakOrContinueStatement(bool IsContinue);

StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
ParsedAttributes &Attrs);
Expand Down
13 changes: 10 additions & 3 deletions clang/include/clang/Sema/ScopeInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ class FunctionScopeInfo {
/// Whether this function contains any indirect gotos.
bool HasIndirectGoto : 1;

/// Whether this function contains any labeled break or continue statements.
bool HasLabeledBreakOrContinue : 1;

/// Whether this function contains any statement marked with
/// \c [[clang::musttail]].
bool HasMustTail : 1;
Expand Down Expand Up @@ -391,7 +394,8 @@ class FunctionScopeInfo {
public:
FunctionScopeInfo(DiagnosticsEngine &Diag)
: Kind(SK_Function), HasBranchProtectedScope(false),
HasBranchIntoScope(false), HasIndirectGoto(false), HasMustTail(false),
HasBranchIntoScope(false), HasIndirectGoto(false),
HasLabeledBreakOrContinue(false), HasMustTail(false),
HasDroppedStmt(false), HasOMPDeclareReductionCombiner(false),
HasFallthroughStmt(false), UsesFPIntrin(false),
HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false),
Expand Down Expand Up @@ -436,6 +440,8 @@ class FunctionScopeInfo {
HasBranchIntoScope = true;
}

void setHasLabeledBreakOrContinue() { HasLabeledBreakOrContinue = true; }

void setHasBranchProtectedScope() {
HasBranchProtectedScope = true;
}
Expand Down Expand Up @@ -485,8 +491,9 @@ class FunctionScopeInfo {
}

bool NeedsScopeChecking() const {
return !HasDroppedStmt && (HasIndirectGoto || HasMustTail ||
(HasBranchProtectedScope && HasBranchIntoScope));
return !HasDroppedStmt &&
(HasIndirectGoto || HasMustTail || HasLabeledBreakOrContinue ||
(HasBranchProtectedScope && HasBranchIntoScope));
}

// Add a block introduced in this function.
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -11033,8 +11033,10 @@ class Sema final : public SemaBase {
LabelDecl *TheDecl);
StmtResult ActOnIndirectGotoStmt(SourceLocation GotoLoc,
SourceLocation StarLoc, Expr *DestExp);
StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope);
StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope);
StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
LabelDecl *Label, SourceLocation LabelLoc);
StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
LabelDecl *Label, SourceLocation LabelLoc);

struct NamedReturnInfo {
const VarDecl *Candidate;
Expand Down
26 changes: 18 additions & 8 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7407,18 +7407,28 @@ ExpectedStmt ASTNodeImporter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
ToGotoLoc, ToStarLoc, ToTarget);
}

template <typename StmtClass>
static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter,
ASTImporter &Importer, StmtClass *S) {
Error Err = Error::success();
auto ToLoc = NodeImporter.importChecked(Err, S->getKwLoc());
auto ToLabelLoc = S->isLabeled()
? NodeImporter.importChecked(Err, S->getLabelLoc())
: SourceLocation();
auto ToDecl = S->isLabeled()
? NodeImporter.importChecked(Err, S->getLabelDecl())
: nullptr;
if (Err)
return std::move(Err);
return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToDecl);
}

ExpectedStmt ASTNodeImporter::VisitContinueStmt(ContinueStmt *S) {
ExpectedSLoc ToContinueLocOrErr = import(S->getContinueLoc());
if (!ToContinueLocOrErr)
return ToContinueLocOrErr.takeError();
return new (Importer.getToContext()) ContinueStmt(*ToContinueLocOrErr);
return ImportLoopControlStmt(*this, Importer, S);
}

ExpectedStmt ASTNodeImporter::VisitBreakStmt(BreakStmt *S) {
auto ToBreakLocOrErr = import(S->getBreakLoc());
if (!ToBreakLocOrErr)
return ToBreakLocOrErr.takeError();
return new (Importer.getToContext()) BreakStmt(*ToBreakLocOrErr);
return ImportLoopControlStmt(*this, Importer, S);
}

ExpectedStmt ASTNodeImporter::VisitReturnStmt(ReturnStmt *S) {
Expand Down
Loading
Loading