https://github.com/LorenzoMauro updated 
https://github.com/llvm/llvm-project/pull/137544

>From 23f729b051867975accda76024f6d9b74b6d3ca4 Mon Sep 17 00:00:00 2001
From: Lorenzo <[email protected]>
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         | 140 ++++++++++++++++++
 clang/unittests/Format/FormatTestComments.cpp |  60 ++++++++
 9 files changed, 318 insertions(+), 5 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst 
b/clang/docs/ClangFormatStyleOptions.rst
index 4f81a084dd65b..e95f18403c373 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2132,6 +2132,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 c7e57d47f9ed1..4741276124aee 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1254,6 +1254,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 ``:``
@@ -5684,6 +5723,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 f0e9aff2fd21a..15a5d27eaf697 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1133,6 +1133,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);
@@ -1681,6 +1683,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 56abd702aaafe..0fc02818ef291 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -323,11 +323,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;
@@ -387,6 +387,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 3208d8e28dd86..ae3b93a57f185 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1982,6 +1982,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)
@@ -2079,6 +2092,7 @@ class AnnotatingParser {
       if (ctx.ContextType == Context::StructArrayInitializer)
         return LT_ArrayOfStructInitializer;
 
+    markTokenAsTemplateArgumentInLine();
     return LT_Other;
   }
 
@@ -5714,6 +5728,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 fec1c48c448d2..b0573c2ec15ba 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -497,6 +497,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 3ee7ce38578aa..3920a38d3bd33 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -9049,6 +9049,146 @@ TEST_F(FormatTest, 
FormatsOneParameterPerLineIfNecessary) {
       NoBinPacking);
 }
 
+TEST_F(FormatTest, FormatsDeclarationBreakAlways) {
+  FormatStyle BreakAlways = getGoogleStyle();
+  BreakAlways.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+  verifyFormat("void f(int a,\n"
+               "       int b);",
+               BreakAlways);
+  verifyFormat("void f(int aaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+               "       int bbbbbbbbbbbbbbbbbbbbbbbbb,\n"
+               "       int cccccccccccccccccccccccc);",
+               BreakAlways);
+
+  // Ensure AlignAfterOpenBracket interacts correctly with BinPackParameters 
set
+  // to BPPS_AlwaysOnePerLine.
+  BreakAlways.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  verifyFormat(
+      "void someLongFunctionName(\n"
+      "    int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+      "    int b);",
+      BreakAlways);
+  BreakAlways.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent;
+  verifyFormat(
+      "void someLongFunctionName(\n"
+      "    int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+      "    int b\n"
+      ");",
+      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;
+  verifyFormat("void f(int a,\n"
+               "       int b) {\n"
+               "  f(a, b);\n"
+               "}",
+               BreakAlways);
+
+  // Ensure BinPackArguments interact correctly when BinPackParameters is set 
to
+  // BPPS_AlwaysOnePerLine.
+  verifyFormat("void f(int aaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+               "       int bbbbbbbbbbbbbbbbbbbbbbbbb,\n"
+               "       int cccccccccccccccccccccccc) {\n"
+               "  f(aaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbb,\n"
+               "    cccccccccccccccccccccccc);\n"
+               "}",
+               BreakAlways);
+  BreakAlways.BinPackArguments = false;
+  verifyFormat("void f(int aaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+               "       int bbbbbbbbbbbbbbbbbbbbbbbbb,\n"
+               "       int cccccccccccccccccccccccc) {\n"
+               "  f(aaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+               "    bbbbbbbbbbbbbbbbbbbbbbbbb,\n"
+               "    cccccccccccccccccccccccc);\n"
+               "}",
+               BreakAlways);
+
+  // Ensure BreakFunctionDefinitionParameters interacts correctly when
+  // BinPackParameters is set to BPPS_AlwaysOnePerLine.
+  BreakAlways.BreakFunctionDefinitionParameters = true;
+  verifyFormat("void f(\n"
+               "    int a,\n"
+               "    int b) {\n"
+               "  f(a, b);\n"
+               "}",
+               BreakAlways);
+  BreakAlways.BreakFunctionDefinitionParameters = false;
+
+  // Ensure AlignAfterOpenBracket interacts correctly with BinPackParameters 
set
+  // to BPPS_AlwaysOnePerLine.
+  BreakAlways.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  verifyFormat(
+      "void someLongFunctionName(\n"
+      "    int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+      "    int b) {\n"
+      "  someLongFunctionName(\n"
+      "      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b);\n"
+      "}",
+      BreakAlways);
+  BreakAlways.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent;
+  verifyFormat(
+      "void someLongFunctionName(\n"
+      "    int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+      "    int b\n"
+      ") {\n"
+      "  someLongFunctionName(\n"
+      "      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b\n"
+      "  );\n"
+      "}",
+      BreakAlways);
+}
+
 TEST_F(FormatTest, AdaptiveOnePerLineFormatting) {
   FormatStyle Style = getLLVMStyleWithColumns(15);
   Style.ExperimentalAutoDetectBinPacking = true;
diff --git a/clang/unittests/Format/FormatTestComments.cpp 
b/clang/unittests/Format/FormatTestComments.cpp
index 684d3014fa7bb..ff9e62fc89d73 100644
--- a/clang/unittests/Format/FormatTestComments.cpp
+++ b/clang/unittests/Format/FormatTestComments.cpp
@@ -428,6 +428,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) {
   verifyFormat("/*\n"
                " * Really multi-line\n"

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to