[clang] [clang-format] Add ApplyAlwaysOnePerLineToTemplateArguments option (PR #137544)
Lorenzo Mauro via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 27 13:22:17 PDT 2025
https://github.com/LorenzoMauro updated https://github.com/llvm/llvm-project/pull/137544
>From b4ea6eebd552436b91bb077c138bf788133f31c3 Mon Sep 17 00:00:00 2001
From: Lorenzo <lorenzo.lomar.mauro at gmail.com>
Date: Sun, 27 Apr 2025 20:24:58 +0200
Subject: [PATCH] [clang-format] Add ApplyAlwaysOnePerLineToTemplateArguments
option
Introduce a new FormatStyle option, ApplyAlwaysOnePerLineToTemplateArguments,
which controls whether BinPackParameters=AlwaysOnePerLine also applies to template
argument lists. This allows users to enforce one-per-line for function parameters
without unintentionally splitting template parameters.
Includes unit tests covering both function declarations and definitions, with and
without trailing comments.
---
clang/docs/ClangFormatStyleOptions.rst | 41 +++++++++++++
clang/include/clang/Format/Format.h | 41 +++++++++++++
clang/lib/Format/Format.cpp | 3 +
clang/lib/Format/FormatToken.h | 13 ++--
clang/lib/Format/TokenAnnotator.cpp | 16 +++++
clang/lib/Format/TokenAnnotator.h | 3 +
clang/unittests/Format/ConfigParseTest.cpp | 6 ++
clang/unittests/Format/FormatTest.cpp | 49 +++++++++++++++
clang/unittests/Format/FormatTestComments.cpp | 60 +++++++++++++++++++
9 files changed, 227 insertions(+), 5 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 3f8a5f49313b2..f0e662d1fac5e 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2140,6 +2140,47 @@ the configuration (without a prefix: ``Auto``).
**AlwaysBreakTemplateDeclarations** (``deprecated``) :versionbadge:`clang-format 3.4` :ref:`¶ <AlwaysBreakTemplateDeclarations>`
This option is renamed to ``BreakTemplateDeclarations``.
+.. _ApplyAlwaysOnePerLineToTemplateArguments:
+
+**ApplyAlwaysOnePerLineToTemplateArguments** (``Boolean``) :versionbadge:`clang-format 21` :ref:`¶ <ApplyAlwaysOnePerLineToTemplateArguments>`
+ If ``BinPackParameters`` is set to ``AlwaysOnePerLine``, specifies whether
+ template argument lists should also be split across multiple lines.
+
+ When set to ``true``, each template argument will be placed on its own
+ line. When set to ``false``, template argument lists remain compact even
+ when function parameters are broken one per line.
+
+
+ .. code-block:: c++
+
+ true:
+ template <typename T, int N>
+ struct Foo {
+ T mData[N];
+
+ Foo<T,
+ N>
+ operator+(const Foo<T,
+ N> &other) const {}
+
+ Foo<T,
+ N>
+ bar(const Foo<T,
+ N> &other,
+ float t) const {}
+ };
+
+ false:
+ template <typename T, int N>
+ struct Foo {
+ T mData[N];
+
+ Foo<T, N> operator+(const Foo<T, N> &other) const {}
+
+ Foo<T, N> bar(const Foo<T, N> &other,
+ float t) const {}
+ };
+
.. _AttributeMacros:
**AttributeMacros** (``List of Strings``) :versionbadge:`clang-format 12` :ref:`¶ <AttributeMacros>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index f6ceef08b46da..a0537106e9abc 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1259,6 +1259,45 @@ struct FormatStyle {
/// \version 3.7
BinPackParametersStyle BinPackParameters;
+ /// If ``BinPackParameters`` is set to ``AlwaysOnePerLine``, specifies whether
+ /// template argument lists should also be split across multiple lines.
+ ///
+ /// When set to ``true``, each template argument will be placed on its own
+ /// line. When set to ``false``, template argument lists remain compact even
+ /// when function parameters are broken one per line.
+ ///
+ /// \code
+ /// true:
+ /// template <typename T, int N>
+ /// struct Foo {
+ /// T mData[N];
+ ///
+ /// Foo<T,
+ /// N>
+ /// operator+(const Foo<T,
+ /// N> &other) const {}
+ ///
+ /// Foo<T,
+ /// N>
+ /// bar(const Foo<T,
+ /// N> &other,
+ /// float t) const {}
+ /// };
+ ///
+ /// false:
+ /// template <typename T, int N>
+ /// struct Foo {
+ /// T mData[N];
+ ///
+ /// Foo<T, N> operator+(const Foo<T, N> &other) const {}
+ ///
+ /// Foo<T, N> bar(const Foo<T, N> &other,
+ /// float t) const {}
+ /// };
+ /// \endcode
+ /// \version 21
+ bool ApplyAlwaysOnePerLineToTemplateArguments;
+
/// Styles for adding spacing around ``:`` in bitfield definitions.
enum BitFieldColonSpacingStyle : int8_t {
/// Add one space on each side of the ``:``
@@ -5326,6 +5365,8 @@ struct FormatStyle {
BinPackArguments == R.BinPackArguments &&
BinPackLongBracedList == R.BinPackLongBracedList &&
BinPackParameters == R.BinPackParameters &&
+ ApplyAlwaysOnePerLineToTemplateArguments ==
+ R.ApplyAlwaysOnePerLineToTemplateArguments &&
BitFieldColonSpacing == R.BitFieldColonSpacing &&
BracedInitializerIndentWidth == R.BracedInitializerIndentWidth &&
BreakAdjacentStringLiterals == R.BreakAdjacentStringLiterals &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 5a1c3f556b331..13f3bc3db3cdc 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1007,6 +1007,8 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("BinPackArguments", Style.BinPackArguments);
IO.mapOptional("BinPackLongBracedList", Style.BinPackLongBracedList);
IO.mapOptional("BinPackParameters", Style.BinPackParameters);
+ IO.mapOptional("ApplyAlwaysOnePerLineToTemplateArguments",
+ Style.ApplyAlwaysOnePerLineToTemplateArguments);
IO.mapOptional("BitFieldColonSpacing", Style.BitFieldColonSpacing);
IO.mapOptional("BracedInitializerIndentWidth",
Style.BracedInitializerIndentWidth);
@@ -1521,6 +1523,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.BinPackArguments = true;
LLVMStyle.BinPackLongBracedList = true;
LLVMStyle.BinPackParameters = FormatStyle::BPPS_BinPack;
+ LLVMStyle.ApplyAlwaysOnePerLineToTemplateArguments = true;
LLVMStyle.BitFieldColonSpacing = FormatStyle::BFCS_Both;
LLVMStyle.BracedInitializerIndentWidth = -1;
LLVMStyle.BraceWrapping = {/*AfterCaseLabel=*/false,
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 946cd7b81587f..7f15c48f15bc3 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -309,11 +309,11 @@ struct FormatToken {
IsUnterminatedLiteral(false), CanBreakBefore(false),
ClosesTemplateDeclaration(false), StartsBinaryExpression(false),
EndsBinaryExpression(false), PartOfMultiVariableDeclStmt(false),
- ContinuesLineCommentSection(false), Finalized(false),
- ClosesRequiresClause(false), EndsCppAttributeGroup(false),
- BlockKind(BK_Unknown), Decision(FD_Unformatted),
- PackingKind(PPK_Inconclusive), TypeIsFinalized(false),
- Type(TT_Unknown) {}
+ InTemplateArgumentList(false), ContinuesLineCommentSection(false),
+ Finalized(false), ClosesRequiresClause(false),
+ EndsCppAttributeGroup(false), BlockKind(BK_Unknown),
+ Decision(FD_Unformatted), PackingKind(PPK_Inconclusive),
+ TypeIsFinalized(false), Type(TT_Unknown) {}
/// The \c Token.
Token Tok;
@@ -373,6 +373,9 @@ struct FormatToken {
/// Only set if \c Type == \c TT_StartOfName.
unsigned PartOfMultiVariableDeclStmt : 1;
+ /// \c true if this token is part of a template argument list.
+ unsigned InTemplateArgumentList : 1;
+
/// Does this line comment continue a line comment section?
///
/// Only set to true if \c Type == \c TT_LineComment.
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index e56cc92987af7..b39c4b2bb609c 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1977,6 +1977,19 @@ class AnnotatingParser {
return Type;
}
+ void markTokenAsTemplateArgumentInLine() {
+ int TemplateDepth = 0;
+ for (FormatToken *Tok = Line.First; Tok; Tok = Tok->Next) {
+ if (Tok->is(TT_TemplateCloser))
+ --TemplateDepth;
+
+ Tok->InTemplateArgumentList = (TemplateDepth > 0);
+
+ if (Tok->is(TT_TemplateOpener))
+ ++TemplateDepth;
+ }
+ }
+
public:
LineType parseLine() {
if (!CurrentToken)
@@ -2074,6 +2087,7 @@ class AnnotatingParser {
if (ctx.ContextType == Context::StructArrayInitializer)
return LT_ArrayOfStructInitializer;
+ markTokenAsTemplateArgumentInLine();
return LT_Other;
}
@@ -5619,6 +5633,8 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
// BreakFunctionDefinitionParameters or AlignAfterOpenBracket.
if (Style.BinPackParameters == FormatStyle::BPPS_AlwaysOnePerLine &&
Line.MightBeFunctionDecl && !Left.opensScope() &&
+ (Style.ApplyAlwaysOnePerLineToTemplateArguments ||
+ !Left.InTemplateArgumentList) &&
startsNextParameter(Right, Style)) {
return true;
}
diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h
index e4b94431e68b4..4d72916dc64a7 100644
--- a/clang/lib/Format/TokenAnnotator.h
+++ b/clang/lib/Format/TokenAnnotator.h
@@ -186,6 +186,9 @@ class AnnotatedLine {
bool MightBeFunctionDecl;
bool IsMultiVariableDeclStmt;
+ /// \c True if this token is part o a template declaration.
+ bool InTemplateDecl = false;
+
/// \c True if this line contains a macro call for which an expansion exists.
bool ContainsMacroCall = false;
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 2b08b794792e9..31f1cbe8e4bf7 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -482,6 +482,12 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("BinPackParameters: false", BinPackParameters,
FormatStyle::BPPS_OnePerLine);
+ Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
+ CHECK_PARSE("ApplyAlwaysOnePerLineToTemplateArguments: true",
+ ApplyAlwaysOnePerLineToTemplateArguments, true);
+ CHECK_PARSE("ApplyAlwaysOnePerLineToTemplateArguments: false",
+ ApplyAlwaysOnePerLineToTemplateArguments, false);
+
Style.PackConstructorInitializers = FormatStyle::PCIS_BinPack;
CHECK_PARSE("PackConstructorInitializers: Never", PackConstructorInitializers,
FormatStyle::PCIS_Never);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 333d40d481025..a50e1cc9b3d38 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -9024,6 +9024,55 @@ TEST_F(FormatTest, FormatsDeclarationBreakAlways) {
BreakAlways);
}
+TEST_F(FormatTest, ApplyAlwaysOnePerLineToTemplateArguments) {
+ FormatStyle Style = getGoogleStyle();
+ Style.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+
+ // Case 1: Template arguments split by AlwaysOnePerLine
+ Style.ApplyAlwaysOnePerLineToTemplateArguments = true;
+ verifyFormat("template <typename T, int N>\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T,\n"
+ " N>\n"
+ " operator+(const Foo<T,\n"
+ " N> &other) const {}\n"
+ " Foo<T,\n"
+ " N>\n"
+ " bar(const Foo<T,\n"
+ " N> &other,\n"
+ " float t) const {}\n"
+ "};\n",
+ Style);
+
+ // Case 2: Template arguments not split by The
+ // ApplyAlwaysOnePerLineToTemplateArguments
+ Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
+ verifyFormat("template <typename T, int N>\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T, N> operator+(const Foo<T, N> &other) const {}\n"
+ " Foo<T, N> bar(const Foo<T, N> &other,\n"
+ " float t) const {}\n"
+ "};\n",
+ Style);
+
+ // Case 3: Template arguments not split by the
+ // ApplyAlwaysOnePerLineToTemplateArguments but using the
+ // BreakFunctionDefinitionParameters flag
+ Style.BreakFunctionDefinitionParameters = true;
+ verifyFormat("template <typename T, int N>\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T, N> operator+(\n"
+ " const Foo<T, N> &other) const {}\n"
+ " Foo<T, N> bar(\n"
+ " const Foo<T, N> &other,\n"
+ " float t) const {}\n"
+ "};\n",
+ Style);
+}
+
TEST_F(FormatTest, FormatsDefinitionBreakAlways) {
FormatStyle BreakAlways = getGoogleStyle();
BreakAlways.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp
index 5eefd767706a3..9b04a3cfc7553 100644
--- a/clang/unittests/Format/FormatTestComments.cpp
+++ b/clang/unittests/Format/FormatTestComments.cpp
@@ -444,6 +444,66 @@ TEST_F(FormatTestComments, UnderstandsBlockComments) {
" int jjj; /*b*/");
}
+TEST_F(FormatTestComments,
+ AlwaysOnePerLineRespectsTemplateArgumentsFlagWithComments) {
+ FormatStyle Style = getGoogleStyle();
+ Style.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+
+ // Case 1: Template arguments split by AlwaysOnePerLine
+ Style.ApplyAlwaysOnePerLineToTemplateArguments = true;
+ verifyFormat("template <typename T, // comment\n"
+ " int N> // comment\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T,\n"
+ " N>\n"
+ " operator+(const Foo<T,\n"
+ " N> &other) const { // comment\n"
+ " }\n"
+ " Foo<T,\n"
+ " N>\n"
+ " bar(const Foo<T,\n"
+ " N> &other, // comment\n"
+ " float t) const { // comment\n"
+ " }\n"
+ "};\n",
+ Style);
+
+ // Case 2: Template arguments not split by The
+ // ApplyAlwaysOnePerLineToTemplateArguments
+ Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
+ verifyFormat(
+ "template <typename T, // comment\n"
+ " int N> // comment\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T, N> operator+(const Foo<T, N> &other) const { // comment\n"
+ " }\n"
+ " Foo<T, N> bar(const Foo<T, N> &other, // comment\n"
+ " float t) const { // comment\n"
+ " }\n"
+ "};\n",
+ Style);
+
+ // Case 3: Template arguments not split by the
+ // ApplyAlwaysOnePerLineToTemplateArguments but using the
+ // BreakFunctionDefinitionParameters flag
+ Style.BreakFunctionDefinitionParameters = true;
+ verifyFormat("template <typename T, // comment\n"
+ " int N> // comment\n"
+ "struct Foo {\n"
+ " T mData[N];\n"
+ " Foo<T, N> operator+(\n"
+ " const Foo<T, N> &other) const { // comment\n"
+ " }\n"
+ " Foo<T, N> bar(\n"
+ " const Foo<T, N> &other, // comment\n"
+ " float t) const { // comment\n"
+ " }\n"
+ "};\n",
+ Style);
+}
+
TEST_F(FormatTestComments, AlignsBlockComments) {
EXPECT_EQ("/*\n"
" * Really multi-line\n"
More information about the cfe-commits
mailing list