Skip to content

Commit 9baa261

Browse files
committed
[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.
1 parent daa1e17 commit 9baa261

File tree

8 files changed

+171
-5
lines changed

8 files changed

+171
-5
lines changed

clang/include/clang/Format/Format.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,30 @@ struct FormatStyle {
12591259
/// \version 3.7
12601260
BinPackParametersStyle BinPackParameters;
12611261

1262+
/// If ``BinPackParameters`` is set to ``AlwaysOnePerLine``, specifies whether
1263+
/// template argument lists should also be split across multiple lines.
1264+
///
1265+
/// When set to ``true``, each template argument will be placed on its own
1266+
/// line. When set to ``false``, template argument lists remain compact even
1267+
/// when function parameters are broken one per line.
1268+
///
1269+
/// \code
1270+
/// true:
1271+
/// template <
1272+
/// typename T,
1273+
/// typename U>
1274+
/// void foo(
1275+
/// T a,
1276+
/// U b);
1277+
///
1278+
/// false:
1279+
/// template <typename T, typename U>
1280+
/// void foo(
1281+
/// T a,
1282+
/// U b);
1283+
/// \endcode
1284+
bool ApplyAlwaysOnePerLineToTemplateArguments = false;
1285+
12621286
/// Styles for adding spacing around ``:`` in bitfield definitions.
12631287
enum BitFieldColonSpacingStyle : int8_t {
12641288
/// Add one space on each side of the ``:``
@@ -5326,6 +5350,8 @@ struct FormatStyle {
53265350
BinPackArguments == R.BinPackArguments &&
53275351
BinPackLongBracedList == R.BinPackLongBracedList &&
53285352
BinPackParameters == R.BinPackParameters &&
5353+
ApplyAlwaysOnePerLineToTemplateArguments ==
5354+
R.ApplyAlwaysOnePerLineToTemplateArguments &&
53295355
BitFieldColonSpacing == R.BitFieldColonSpacing &&
53305356
BracedInitializerIndentWidth == R.BracedInitializerIndentWidth &&
53315357
BreakAdjacentStringLiterals == R.BreakAdjacentStringLiterals &&

clang/lib/Format/Format.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,8 @@ template <> struct MappingTraits<FormatStyle> {
10071007
IO.mapOptional("BinPackArguments", Style.BinPackArguments);
10081008
IO.mapOptional("BinPackLongBracedList", Style.BinPackLongBracedList);
10091009
IO.mapOptional("BinPackParameters", Style.BinPackParameters);
1010+
IO.mapOptional("ApplyAlwaysOnePerLineToTemplateArguments",
1011+
Style.ApplyAlwaysOnePerLineToTemplateArguments);
10101012
IO.mapOptional("BitFieldColonSpacing", Style.BitFieldColonSpacing);
10111013
IO.mapOptional("BracedInitializerIndentWidth",
10121014
Style.BracedInitializerIndentWidth);
@@ -1521,6 +1523,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
15211523
LLVMStyle.BinPackArguments = true;
15221524
LLVMStyle.BinPackLongBracedList = true;
15231525
LLVMStyle.BinPackParameters = FormatStyle::BPPS_BinPack;
1526+
LLVMStyle.ApplyAlwaysOnePerLineToTemplateArguments = true;
15241527
LLVMStyle.BitFieldColonSpacing = FormatStyle::BFCS_Both;
15251528
LLVMStyle.BracedInitializerIndentWidth = -1;
15261529
LLVMStyle.BraceWrapping = {/*AfterCaseLabel=*/false,

clang/lib/Format/FormatToken.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,11 @@ struct FormatToken {
309309
IsUnterminatedLiteral(false), CanBreakBefore(false),
310310
ClosesTemplateDeclaration(false), StartsBinaryExpression(false),
311311
EndsBinaryExpression(false), PartOfMultiVariableDeclStmt(false),
312-
ContinuesLineCommentSection(false), Finalized(false),
313-
ClosesRequiresClause(false), EndsCppAttributeGroup(false),
314-
BlockKind(BK_Unknown), Decision(FD_Unformatted),
315-
PackingKind(PPK_Inconclusive), TypeIsFinalized(false),
316-
Type(TT_Unknown) {}
312+
InTemplateArgumentList(false), ContinuesLineCommentSection(false),
313+
Finalized(false), ClosesRequiresClause(false),
314+
EndsCppAttributeGroup(false), BlockKind(BK_Unknown),
315+
Decision(FD_Unformatted), PackingKind(PPK_Inconclusive),
316+
TypeIsFinalized(false), Type(TT_Unknown) {}
317317

318318
/// The \c Token.
319319
Token Tok;
@@ -373,6 +373,9 @@ struct FormatToken {
373373
/// Only set if \c Type == \c TT_StartOfName.
374374
unsigned PartOfMultiVariableDeclStmt : 1;
375375

376+
/// \c true if this token is part of a template argument list.
377+
unsigned InTemplateArgumentList : 1;
378+
376379
/// Does this line comment continue a line comment section?
377380
///
378381
/// Only set to true if \c Type == \c TT_LineComment.

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,19 @@ class AnnotatingParser {
19771977
return Type;
19781978
}
19791979

1980+
void markTokenAsTemplateArgumentInLine() {
1981+
int TemplateDepth = 0;
1982+
for (FormatToken *Tok = Line.First; Tok; Tok = Tok->Next) {
1983+
if (Tok->is(TT_TemplateCloser))
1984+
--TemplateDepth;
1985+
1986+
Tok->InTemplateArgumentList = (TemplateDepth > 0);
1987+
1988+
if (Tok->is(TT_TemplateOpener))
1989+
++TemplateDepth;
1990+
}
1991+
}
1992+
19801993
public:
19811994
LineType parseLine() {
19821995
if (!CurrentToken)
@@ -2074,6 +2087,7 @@ class AnnotatingParser {
20742087
if (ctx.ContextType == Context::StructArrayInitializer)
20752088
return LT_ArrayOfStructInitializer;
20762089

2090+
markTokenAsTemplateArgumentInLine();
20772091
return LT_Other;
20782092
}
20792093

@@ -5619,6 +5633,8 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
56195633
// BreakFunctionDefinitionParameters or AlignAfterOpenBracket.
56205634
if (Style.BinPackParameters == FormatStyle::BPPS_AlwaysOnePerLine &&
56215635
Line.MightBeFunctionDecl && !Left.opensScope() &&
5636+
(Style.ApplyAlwaysOnePerLineToTemplateArguments ||
5637+
!Left.InTemplateArgumentList) &&
56225638
startsNextParameter(Right, Style)) {
56235639
return true;
56245640
}

clang/lib/Format/TokenAnnotator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ class AnnotatedLine {
186186
bool MightBeFunctionDecl;
187187
bool IsMultiVariableDeclStmt;
188188

189+
/// \c True if this token is part o a template declaration.
190+
bool InTemplateDecl = false;
191+
189192
/// \c True if this line contains a macro call for which an expansion exists.
190193
bool ContainsMacroCall = false;
191194

clang/unittests/Format/ConfigParseTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,12 @@ TEST(ConfigParseTest, ParsesConfiguration) {
482482
CHECK_PARSE("BinPackParameters: false", BinPackParameters,
483483
FormatStyle::BPPS_OnePerLine);
484484

485+
Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
486+
CHECK_PARSE("ApplyAlwaysOnePerLineToTemplateArguments: true",
487+
ApplyAlwaysOnePerLineToTemplateArguments, true);
488+
CHECK_PARSE("ApplyAlwaysOnePerLineToTemplateArguments: false",
489+
ApplyAlwaysOnePerLineToTemplateArguments, false);
490+
485491
Style.PackConstructorInitializers = FormatStyle::PCIS_BinPack;
486492
CHECK_PARSE("PackConstructorInitializers: Never", PackConstructorInitializers,
487493
FormatStyle::PCIS_Never);

clang/unittests/Format/FormatTest.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9024,6 +9024,55 @@ TEST_F(FormatTest, FormatsDeclarationBreakAlways) {
90249024
BreakAlways);
90259025
}
90269026

9027+
TEST_F(FormatTest, ApplyAlwaysOnePerLineToTemplateArguments) {
9028+
FormatStyle Style = getGoogleStyle();
9029+
Style.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
9030+
9031+
// Case 1: Template arguments split by AlwaysOnePerLine
9032+
Style.ApplyAlwaysOnePerLineToTemplateArguments = true;
9033+
verifyFormat("template <typename T, int N>\n"
9034+
"struct Foo {\n"
9035+
" T mData[N];\n"
9036+
" Foo<T,\n"
9037+
" N>\n"
9038+
" operator+(const Foo<T,\n"
9039+
" N> &other) const {}\n"
9040+
" Foo<T,\n"
9041+
" N>\n"
9042+
" bar(const Foo<T,\n"
9043+
" N> &other,\n"
9044+
" float t) const {}\n"
9045+
"};\n",
9046+
Style);
9047+
9048+
// Case 2: Template arguments not split by The
9049+
// ApplyAlwaysOnePerLineToTemplateArguments
9050+
Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
9051+
verifyFormat("template <typename T, int N>\n"
9052+
"struct Foo {\n"
9053+
" T mData[N];\n"
9054+
" Foo<T, N> operator+(const Foo<T, N> &other) const {}\n"
9055+
" Foo<T, N> bar(const Foo<T, N> &other,\n"
9056+
" float t) const {}\n"
9057+
"};\n",
9058+
Style);
9059+
9060+
// Case 3: Template arguments not split by the
9061+
// ApplyAlwaysOnePerLineToTemplateArguments but using the
9062+
// BreakFunctionDefinitionParameters flag
9063+
Style.BreakFunctionDefinitionParameters = true;
9064+
verifyFormat("template <typename T, int N>\n"
9065+
"struct Foo {\n"
9066+
" T mData[N];\n"
9067+
" Foo<T, N> operator+(\n"
9068+
" const Foo<T, N> &other) const {}\n"
9069+
" Foo<T, N> bar(\n"
9070+
" const Foo<T, N> &other,\n"
9071+
" float t) const {}\n"
9072+
"};\n",
9073+
Style);
9074+
}
9075+
90279076
TEST_F(FormatTest, FormatsDefinitionBreakAlways) {
90289077
FormatStyle BreakAlways = getGoogleStyle();
90299078
BreakAlways.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;

clang/unittests/Format/FormatTestComments.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,66 @@ TEST_F(FormatTestComments, UnderstandsBlockComments) {
444444
" int jjj; /*b*/");
445445
}
446446

447+
TEST_F(FormatTestComments,
448+
AlwaysOnePerLineRespectsTemplateArgumentsFlagWithComments) {
449+
FormatStyle Style = getGoogleStyle();
450+
Style.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
451+
452+
// Case 1: Template arguments split by AlwaysOnePerLine
453+
Style.ApplyAlwaysOnePerLineToTemplateArguments = true;
454+
verifyFormat("template <typename T, // comment\n"
455+
" int N> // comment\n"
456+
"struct Foo {\n"
457+
" T mData[N];\n"
458+
" Foo<T,\n"
459+
" N>\n"
460+
" operator+(const Foo<T,\n"
461+
" N> &other) const { // comment\n"
462+
" }\n"
463+
" Foo<T,\n"
464+
" N>\n"
465+
" bar(const Foo<T,\n"
466+
" N> &other, // comment\n"
467+
" float t) const { // comment\n"
468+
" }\n"
469+
"};\n",
470+
Style);
471+
472+
// Case 2: Template arguments not split by The
473+
// ApplyAlwaysOnePerLineToTemplateArguments
474+
Style.ApplyAlwaysOnePerLineToTemplateArguments = false;
475+
verifyFormat(
476+
"template <typename T, // comment\n"
477+
" int N> // comment\n"
478+
"struct Foo {\n"
479+
" T mData[N];\n"
480+
" Foo<T, N> operator+(const Foo<T, N> &other) const { // comment\n"
481+
" }\n"
482+
" Foo<T, N> bar(const Foo<T, N> &other, // comment\n"
483+
" float t) const { // comment\n"
484+
" }\n"
485+
"};\n",
486+
Style);
487+
488+
// Case 3: Template arguments not split by the
489+
// ApplyAlwaysOnePerLineToTemplateArguments but using the
490+
// BreakFunctionDefinitionParameters flag
491+
Style.BreakFunctionDefinitionParameters = true;
492+
verifyFormat("template <typename T, // comment\n"
493+
" int N> // comment\n"
494+
"struct Foo {\n"
495+
" T mData[N];\n"
496+
" Foo<T, N> operator+(\n"
497+
" const Foo<T, N> &other) const { // comment\n"
498+
" }\n"
499+
" Foo<T, N> bar(\n"
500+
" const Foo<T, N> &other, // comment\n"
501+
" float t) const { // comment\n"
502+
" }\n"
503+
"};\n",
504+
Style);
505+
}
506+
447507
TEST_F(FormatTestComments, AlignsBlockComments) {
448508
EXPECT_EQ("/*\n"
449509
" * Really multi-line\n"

0 commit comments

Comments
 (0)