[clang] [clang-format] Fix formatting with consecutive requires clauses (PR #220792)
Abdul Mohammad via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 3 21:55:02 PDT 2026
https://github.com/abdulm5 updated https://github.com/llvm/llvm-project/pull/220792
>From 4eb06a7acbcd37bea72f0d798303e9be5854063e Mon Sep 17 00:00:00 2001
From: abdulm6 <abdul.mohammad9087 at gmail.com>
Date: Wed, 2 Sep 2026 00:06:51 -0700
Subject: [PATCH 1/2] [clang-format] Fix formatting with consecutive requires
clauses
---
clang/docs/ReleaseNotes.md | 2 ++
clang/lib/Format/TokenAnnotator.cpp | 30 ++++++++++++++-----
clang/unittests/Format/FormatTest.cpp | 7 +++++
clang/unittests/Format/TokenAnnotatorTest.cpp | 14 +++++++++
4 files changed, 46 insertions(+), 7 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 4c2bf55f6ebdd..782eb5602f886 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -741,6 +741,8 @@ features cannot lower the translation-unit ABI level;
- Add `AfterRequiresExpression` sub-option of `BraceWrapping` to wrap the
body of requires expressions. It is enabled by the `Allman`, `Whitesmiths`,
and `GNU` styles of `BreakBeforeBraces`.
+- Fixed incorrect indentation of consecutive template declarations with
+ requires clauses. (#GH219801)
- `QualifierOrder` now supports `typedef`, `consteval`, `constinit`,
`thread_local`, `extern`, `mutable`, `signed`, `unsigned`, `long`, `short`,
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 105c98e704c3b..6d41074c76839 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -3259,14 +3259,17 @@ class ExpressionParser {
void parse(int Precedence = 0) {
// Skip 'return' and ObjC selector colons as they are not part of a binary
// expression.
- while (Current && (Current->is(tok::kw_return) ||
- (Current->is(tok::colon) &&
- Current->isOneOf(TT_ObjCMethodExpr, TT_DictLiteral)))) {
+ while (Current && Current != RequiresClauseLimit &&
+ (Current->is(tok::kw_return) ||
+ (Current->is(tok::colon) &&
+ Current->isOneOf(TT_ObjCMethodExpr, TT_DictLiteral)))) {
next();
}
- if (!Current || Precedence > PrecedenceArrowAndPeriod)
+ if (!Current || Current == RequiresClauseLimit ||
+ Precedence > PrecedenceArrowAndPeriod) {
return;
+ }
// Conditional expressions need to be parsed separately for proper nesting.
if (Precedence == prec::Conditional) {
@@ -3287,7 +3290,7 @@ class ExpressionParser {
// The first name of the current type in a port list.
FormatToken *VerilogFirstOfType = nullptr;
- while (Current) {
+ while (Current && Current != RequiresClauseLimit) {
// In Verilog ports in a module header that don't have a type take the
// type of the previous one. For example,
// module a(output b,
@@ -3301,6 +3304,8 @@ class ExpressionParser {
// Consume operators with higher precedence.
parse(Precedence + 1);
+ if (!Current || Current == RequiresClauseLimit)
+ break;
int CurrentPrecedence = getCurrentPrecedence();
if (CurrentPrecedence > prec::Conditional &&
@@ -3351,9 +3356,18 @@ class ExpressionParser {
// Consume scopes: (), [], <> and {}
// In addition to that we handle require clauses as scope, so that the
// constraints in that are correctly indented.
- if (Current->opensScope() ||
- Current->isOneOf(TT_RequiresClause,
+ if (Current->isOneOf(TT_RequiresClause,
TT_RequiresClauseInARequiresExpression)) {
+ const auto *End = Current;
+ while (End && !End->ClosesRequiresClause)
+ End = End->Next;
+
+ const auto *PreviousLimit = RequiresClauseLimit;
+ RequiresClauseLimit = End ? End->getNextNonComment() : PreviousLimit;
+ next();
+ parse();
+ RequiresClauseLimit = PreviousLimit;
+ } else if (Current->opensScope()) {
// In fragment of a JavaScript template string can look like '}..${' and
// thus close a scope and open a new one at the same time.
while (Current && (!Current->closesScope() || Current->opensScope())) {
@@ -3657,6 +3671,8 @@ class ExpressionParser {
const AdditionalKeywords &Keywords;
const AnnotatedLine &Line;
FormatToken *Current;
+ // The first non-comment token after the requires clause being parsed.
+ const FormatToken *RequiresClauseLimit = nullptr;
};
} // end anonymous namespace
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 5aed37aa56d52..2145bcc0edeee 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24179,6 +24179,13 @@ TEST_F(FormatTest, RequiresClausesPositions) {
// when the default was REI_Keyword.
Style.RequiresExpressionIndentation = FormatStyle::REI_Keyword;
+ verifyFormat("template <int N, int C>\n"
+ " requires(N > 2) && (C > 0)\n"
+ "template <typename U>\n"
+ " requires(sizeof(U) > 0)\n"
+ "void S<N, C>::f(U) {}",
+ Style);
+
verifyFormat("template <typename T>\n"
" requires(Foo<T> && std::trait<T>)\n"
"struct Bar;",
diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp
index b71147aaf1bc2..5dca71507a6e5 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -1949,6 +1949,20 @@ TEST_F(TokenAnnotatorTest, RequiresDoesNotChangeParsingOfTheRest) {
RequiresTokenCount = 4;
PrefixTokenCount = 5;
TestRequires(__LINE__);
+
+ BaseCode = "template<typename T>\n"
+ "template<typename U>\n"
+ " requires Bar<U>\n"
+ "void S<T>::f(U) {}";
+ ConstrainedCode = "template<typename T>\n"
+ " requires Foo<T> && Baz<T>\n"
+ "template<typename U>\n"
+ " requires Bar<U>\n"
+ "void S<T>::f(U) {}";
+ BaseTokenCount = 28;
+ RequiresTokenCount = 10;
+ PrefixTokenCount = 5;
+ TestRequires(__LINE__);
}
TEST_F(TokenAnnotatorTest, UnderstandsAsm) {
>From 0cd1d34c8a2a3978f5773c8cd1c86407fea233a2 Mon Sep 17 00:00:00 2001
From: abdulm6 <abdul.mohammad9087 at gmail.com>
Date: Thu, 3 Sep 2026 21:54:39 -0700
Subject: [PATCH 2/2] [clang-format] Address requires-clause review feedback
---
clang/lib/Format/TokenAnnotator.cpp | 45 +++++++++++++++++----------
clang/unittests/Format/FormatTest.cpp | 10 ++++++
2 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 6d41074c76839..7ee5fb8d9fd6e 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -3259,17 +3259,14 @@ class ExpressionParser {
void parse(int Precedence = 0) {
// Skip 'return' and ObjC selector colons as they are not part of a binary
// expression.
- while (Current && Current != RequiresClauseLimit &&
- (Current->is(tok::kw_return) ||
- (Current->is(tok::colon) &&
- Current->isOneOf(TT_ObjCMethodExpr, TT_DictLiteral)))) {
+ while (Current && (Current->is(tok::kw_return) ||
+ (Current->is(tok::colon) &&
+ Current->isOneOf(TT_ObjCMethodExpr, TT_DictLiteral)))) {
next();
}
- if (!Current || Current == RequiresClauseLimit ||
- Precedence > PrecedenceArrowAndPeriod) {
+ if (!Current || Precedence > PrecedenceArrowAndPeriod)
return;
- }
// Conditional expressions need to be parsed separately for proper nesting.
if (Precedence == prec::Conditional) {
@@ -3290,7 +3287,7 @@ class ExpressionParser {
// The first name of the current type in a port list.
FormatToken *VerilogFirstOfType = nullptr;
- while (Current && Current != RequiresClauseLimit) {
+ while (Current && Current != RequiresClauseEnd) {
// In Verilog ports in a module header that don't have a type take the
// type of the previous one. For example,
// module a(output b,
@@ -3304,7 +3301,8 @@ class ExpressionParser {
// Consume operators with higher precedence.
parse(Precedence + 1);
- if (!Current || Current == RequiresClauseLimit)
+ // The recursive call may have reached the end of a requires clause.
+ if (Current == RequiresClauseEnd)
break;
int CurrentPrecedence = getCurrentPrecedence();
@@ -3358,15 +3356,12 @@ class ExpressionParser {
// constraints in that are correctly indented.
if (Current->isOneOf(TT_RequiresClause,
TT_RequiresClauseInARequiresExpression)) {
- const auto *End = Current;
- while (End && !End->ClosesRequiresClause)
- End = End->Next;
-
- const auto *PreviousLimit = RequiresClauseLimit;
- RequiresClauseLimit = End ? End->getNextNonComment() : PreviousLimit;
+ const auto *PreviousEnd = RequiresClauseEnd;
+ if (const auto *End = findRequiresClauseEnd(Current))
+ RequiresClauseEnd = End;
next();
parse();
- RequiresClauseLimit = PreviousLimit;
+ RequiresClauseEnd = PreviousEnd;
} else if (Current->opensScope()) {
// In fragment of a JavaScript template string can look like '}..${' and
// thus close a scope and open a new one at the same time.
@@ -3418,6 +3413,22 @@ class ExpressionParser {
}
private:
+ const FormatToken *findRequiresClauseEnd(const FormatToken *Requires) const {
+ unsigned NestedRequires = 0;
+ for (const auto *Token = Requires->Next; Token; Token = Token->Next) {
+ if (Token->is(TT_RequiresClause))
+ ++NestedRequires;
+ if (!Token->ClosesRequiresClause)
+ continue;
+ if (NestedRequires > 0) {
+ --NestedRequires;
+ continue;
+ }
+ return Token->getNextNonComment();
+ }
+ return nullptr;
+ }
+
/// Gets the precedence (+1) of the given token for binary operators
/// and other tokens that we treat like binary operators.
int getCurrentPrecedence() {
@@ -3672,7 +3683,7 @@ class ExpressionParser {
const AnnotatedLine &Line;
FormatToken *Current;
// The first non-comment token after the requires clause being parsed.
- const FormatToken *RequiresClauseLimit = nullptr;
+ const FormatToken *RequiresClauseEnd = nullptr;
};
} // end anonymous namespace
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 2145bcc0edeee..6675c55ecd603 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24186,6 +24186,16 @@ TEST_F(FormatTest, RequiresClausesPositions) {
"void S<N, C>::f(U) {}",
Style);
+ verifyFormat("template <typename T>\n"
+ " requires([]<typename V>\n"
+ " requires Foo<V>\n"
+ " {}()) &&\n"
+ " Bar<T>\n"
+ "template <typename U>\n"
+ " requires Baz<U>\n"
+ "void S<T>::f(U) {}",
+ Style);
+
verifyFormat("template <typename T>\n"
" requires(Foo<T> && std::trait<T>)\n"
"struct Bar;",
More information about the cfe-commits
mailing list