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

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 formatting for function parameters 
without unintentionally splitting template arguments.

Includes unit tests covering function declarations, definitions, and template 
instantiations, both with and without trailing comments.

---

## Behavior

If `BinPackParameters` is set to `AlwaysOnePerLine`, this option controls 
whether template arguments are split across multiple lines.

- When `true`, which is the current only behaviour, each template argument is 
placed on its own line:

```cpp
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 {}
};

```

- When `false`, template argument lists remain compact even if function 
parameters are broken one per line:

```cpp
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 {}
};
```

>From 9baa26111acaca5ec2f741aba2f7f4ce9b811c4e Mon Sep 17 00:00:00 2001
From: Lorenzo <lorenzo.lomar.ma...@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/include/clang/Format/Format.h           | 26 ++++++++
 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 +++++++++++++++++++
 8 files changed, 171 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index f6ceef08b46da..81dafb53b1e89 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1259,6 +1259,30 @@ 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,
+  ///     typename U>
+  /// void foo(
+  ///     T a,
+  ///     U b);
+  ///
+  /// false:
+  /// template <typename T, typename U>
+  /// void foo(
+  ///     T a,
+  ///     U b);
+  /// \endcode
+  bool ApplyAlwaysOnePerLineToTemplateArguments = false;
+
   /// Styles for adding spacing around ``:`` in bitfield definitions.
   enum BitFieldColonSpacingStyle : int8_t {
     /// Add one space on each side of the ``:``
@@ -5326,6 +5350,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"

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to