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
