[clang] d2b45ce - [clang-format] Add BreakBeforeTemplateCloser option (#118046)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 6 01:15:51 PST 2025
Author: leijurv
Date: 2025-02-06T01:15:47-08:00
New Revision: d2b45ce100d641a8f1690e30843bb9c5ea71ab86
URL: https://github.com/llvm/llvm-project/commit/d2b45ce100d641a8f1690e30843bb9c5ea71ab86
DIFF: https://github.com/llvm/llvm-project/commit/d2b45ce100d641a8f1690e30843bb9c5ea71ab86.diff
LOG: [clang-format] Add BreakBeforeTemplateCloser option (#118046)
In clang-format, multiline templates have the `>` on the same line as
the last parameter:
```c++
template <
typename Foo,
typename Bar>
void foo() {
```
I would like to add an option to put the `>` on the next line, like
this:
```c++
template <
typename Foo,
typename Bar
>
void foo() {
```
An example of a large project that uses this style is NVIDIA's CUTLASS,
here is an example:
https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/epilogue/dispatch_policy.hpp#L149-L156
My reasoning is that it reminds me of this style of braces:
```c++
if (foo()) {
bar();
baz();}
```
Most people agree this is better:
```c++
if (foo()) {
bar();
baz();
}
```
---------
Co-authored-by: Owen Pan <owenpiano at gmail.com>
Added:
Modified:
clang/docs/ClangFormatStyleOptions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Format/Format.h
clang/lib/Format/ContinuationIndenter.cpp
clang/lib/Format/ContinuationIndenter.h
clang/lib/Format/Format.cpp
clang/lib/Format/TokenAnnotator.cpp
clang/unittests/Format/ConfigParseTest.cpp
clang/unittests/Format/FormatTest.cpp
Removed:
################################################################################
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index bbb912eb10e94d7..ce38a3a9ba1f739 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -3421,6 +3421,35 @@ the configuration (without a prefix: ``Auto``).
+.. _BreakBeforeTemplateCloser:
+
+**BreakBeforeTemplateCloser** (``Boolean``) :versionbadge:`clang-format 21` :ref:`¶ <BreakBeforeTemplateCloser>`
+ If ``true``, break before a template closing bracket (``>``) when there is
+ a line break after the matching opening bracket (``<``).
+
+ .. code-block:: c++
+
+ true:
+ template <typename Foo, typename Bar>
+
+ template <typename Foo,
+ typename Bar>
+
+ template <
+ typename Foo,
+ typename Bar
+ >
+
+ false:
+ template <typename Foo, typename Bar>
+
+ template <typename Foo,
+ typename Bar>
+
+ template <
+ typename Foo,
+ typename Bar>
+
.. _BreakBeforeTernaryOperators:
**BreakBeforeTernaryOperators** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ <BreakBeforeTernaryOperators>`
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index bd26c41a41793f7..159b45946d7051f 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -231,6 +231,8 @@ AST Matchers
clang-format
------------
+- Adds ``BreakBeforeTemplateCloser`` option.
+
libclang
--------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 6f432d1d5031542..fbc9291ae950d41 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2252,6 +2252,33 @@ struct FormatStyle {
/// \version 16
BreakBeforeInlineASMColonStyle BreakBeforeInlineASMColon;
+ /// If ``true``, break before a template closing bracket (``>``) when there is
+ /// a line break after the matching opening bracket (``<``).
+ /// \code
+ /// true:
+ /// template <typename Foo, typename Bar>
+ ///
+ /// template <typename Foo,
+ /// typename Bar>
+ ///
+ /// template <
+ /// typename Foo,
+ /// typename Bar
+ /// >
+ ///
+ /// false:
+ /// template <typename Foo, typename Bar>
+ ///
+ /// template <typename Foo,
+ /// typename Bar>
+ ///
+ /// template <
+ /// typename Foo,
+ /// typename Bar>
+ /// \endcode
+ /// \version 21
+ bool BreakBeforeTemplateCloser;
+
/// If ``true``, ternary operators will be placed after line breaks.
/// \code
/// true:
@@ -5251,6 +5278,7 @@ struct FormatStyle {
BreakBeforeBraces == R.BreakBeforeBraces &&
BreakBeforeConceptDeclarations == R.BreakBeforeConceptDeclarations &&
BreakBeforeInlineASMColon == R.BreakBeforeInlineASMColon &&
+ BreakBeforeTemplateCloser == R.BreakBeforeTemplateCloser &&
BreakBeforeTernaryOperators == R.BreakBeforeTernaryOperators &&
BreakBinaryOperations == R.BreakBinaryOperations &&
BreakConstructorInitializers == R.BreakConstructorInitializers &&
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index 6f7d213c0b55996..3e51b4aab10824a 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -349,6 +349,13 @@ bool ContinuationIndenter::canBreak(const LineState &State) {
}
}
+ // Don't allow breaking before a closing brace of a block-indented braced list
+ // initializer if there isn't already a break.
+ if (Current.is(tok::r_brace) && Current.MatchingParen &&
+ Current.isBlockIndentedInitRBrace(Style)) {
+ return CurrentState.BreakBeforeClosingBrace;
+ }
+
// Allow breaking before the right parens with block indentation if there was
// a break after the left parens, which is tracked by BreakBeforeClosingParen.
if (Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent &&
@@ -356,12 +363,8 @@ bool ContinuationIndenter::canBreak(const LineState &State) {
return CurrentState.BreakBeforeClosingParen;
}
- // Don't allow breaking before a closing brace of a block-indented braced list
- // initializer if there isn't already a break.
- if (Current.is(tok::r_brace) && Current.MatchingParen &&
- Current.isBlockIndentedInitRBrace(Style)) {
- return CurrentState.BreakBeforeClosingBrace;
- }
+ if (Style.BreakBeforeTemplateCloser && Current.is(TT_TemplateCloser))
+ return CurrentState.BreakBeforeClosingAngle;
// If binary operators are moved to the next line (including commas for some
// styles of constructor initializers), that's always ok.
@@ -414,6 +417,8 @@ bool ContinuationIndenter::mustBreak(const LineState &State) {
}
if (CurrentState.BreakBeforeClosingParen && Current.is(tok::r_paren))
return true;
+ if (CurrentState.BreakBeforeClosingAngle && Current.is(TT_TemplateCloser))
+ return true;
if (Style.Language == FormatStyle::LK_ObjC &&
Style.ObjCBreakBeforeNestedBlockParam &&
Current.ObjCSelectorNameParts > 1 &&
@@ -1243,6 +1248,9 @@ unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State,
Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent;
}
+ if (PreviousNonComment && PreviousNonComment->is(TT_TemplateOpener))
+ CurrentState.BreakBeforeClosingAngle = Style.BreakBeforeTemplateCloser;
+
if (CurrentState.AvoidBinPacking) {
// If we are breaking after '(', '{', '<', or this is the break after a ':'
// to start a member initializer list in a constructor, this should not
@@ -1379,6 +1387,10 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) {
State.Stack.size() > 1) {
return State.Stack[State.Stack.size() - 2].LastSpace;
}
+ if (Style.BreakBeforeTemplateCloser && Current.is(TT_TemplateCloser) &&
+ State.Stack.size() > 1) {
+ return State.Stack[State.Stack.size() - 2].LastSpace;
+ }
if (NextNonComment->is(TT_TemplateString) && NextNonComment->closesScope())
return State.Stack[State.Stack.size() - 2].LastSpace;
// Field labels in a nested type should be aligned to the brace. For example
diff --git a/clang/lib/Format/ContinuationIndenter.h b/clang/lib/Format/ContinuationIndenter.h
index 18441e10a124924..ac354aa96f86ea9 100644
--- a/clang/lib/Format/ContinuationIndenter.h
+++ b/clang/lib/Format/ContinuationIndenter.h
@@ -200,14 +200,15 @@ struct ParenState {
: Tok(Tok), Indent(Indent), LastSpace(LastSpace),
NestedBlockIndent(Indent), IsAligned(false),
BreakBeforeClosingBrace(false), BreakBeforeClosingParen(false),
- AvoidBinPacking(AvoidBinPacking), BreakBeforeParameter(false),
- NoLineBreak(NoLineBreak), NoLineBreakInOperand(false),
- LastOperatorWrapped(true), ContainsLineBreak(false),
- ContainsUnwrappedBuilder(false), AlignColons(true),
- ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false),
- NestedBlockInlined(false), IsInsideObjCArrayLiteral(false),
- IsCSharpGenericTypeConstraint(false), IsChainedConditional(false),
- IsWrappedConditional(false), UnindentOperator(false) {}
+ BreakBeforeClosingAngle(false), AvoidBinPacking(AvoidBinPacking),
+ BreakBeforeParameter(false), NoLineBreak(NoLineBreak),
+ NoLineBreakInOperand(false), LastOperatorWrapped(true),
+ ContainsLineBreak(false), ContainsUnwrappedBuilder(false),
+ AlignColons(true), ObjCSelectorNameFound(false),
+ HasMultipleNestedBlocks(false), NestedBlockInlined(false),
+ IsInsideObjCArrayLiteral(false), IsCSharpGenericTypeConstraint(false),
+ IsChainedConditional(false), IsWrappedConditional(false),
+ UnindentOperator(false) {}
/// \brief The token opening this parenthesis level, or nullptr if this level
/// is opened by fake parenthesis.
@@ -280,6 +281,9 @@ struct ParenState {
/// was a newline after the beginning left paren.
bool BreakBeforeClosingParen : 1;
+ /// Whether a newline needs to be inserted before a closing angle `>`.
+ bool BreakBeforeClosingAngle : 1;
+
/// Avoid bin packing, i.e. multiple parameters/elements on multiple
/// lines, in this context.
bool AvoidBinPacking : 1;
@@ -367,6 +371,8 @@ struct ParenState {
return BreakBeforeClosingBrace;
if (BreakBeforeClosingParen != Other.BreakBeforeClosingParen)
return BreakBeforeClosingParen;
+ if (BreakBeforeClosingAngle != Other.BreakBeforeClosingAngle)
+ return BreakBeforeClosingAngle;
if (QuestionColumn != Other.QuestionColumn)
return QuestionColumn < Other.QuestionColumn;
if (AvoidBinPacking != Other.AvoidBinPacking)
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index f02bf95cfeed7ea..387daad934f67ac 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1014,6 +1014,8 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("BreakBeforeBraces", Style.BreakBeforeBraces);
IO.mapOptional("BreakBeforeInlineASMColon",
Style.BreakBeforeInlineASMColon);
+ IO.mapOptional("BreakBeforeTemplateCloser",
+ Style.BreakBeforeTemplateCloser);
IO.mapOptional("BreakBeforeTernaryOperators",
Style.BreakBeforeTernaryOperators);
IO.mapOptional("BreakBinaryOperations", Style.BreakBinaryOperations);
@@ -1535,6 +1537,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach;
LLVMStyle.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Always;
LLVMStyle.BreakBeforeInlineASMColon = FormatStyle::BBIAS_OnlyMultiline;
+ LLVMStyle.BreakBeforeTemplateCloser = false;
LLVMStyle.BreakBeforeTernaryOperators = true;
LLVMStyle.BreakBinaryOperations = FormatStyle::BBO_Never;
LLVMStyle.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon;
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index f25332e3a5f4e1a..94fd7ba9c0e79e3 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6180,6 +6180,9 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
return false;
}
+ if (Right.is(TT_TemplateCloser))
+ return Style.BreakBeforeTemplateCloser;
+
if (Left.is(tok::at))
return false;
if (Left.Tok.getObjCKeywordID() == tok::objc_interface)
@@ -6328,8 +6331,6 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
if (Right.is(TT_ImplicitStringLiteral))
return false;
- if (Right.is(TT_TemplateCloser))
- return false;
if (Right.is(tok::r_square) && Right.MatchingParen &&
Right.MatchingParen->is(TT_LambdaLSquare)) {
return false;
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 5bb1c00ab0bb239..0cb2a1288bfd7ec 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -170,6 +170,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_BOOL(BinPackArguments);
CHECK_PARSE_BOOL(BreakAdjacentStringLiterals);
CHECK_PARSE_BOOL(BreakAfterJavaFieldAnnotations);
+ CHECK_PARSE_BOOL(BreakBeforeTemplateCloser);
CHECK_PARSE_BOOL(BreakBeforeTernaryOperators);
CHECK_PARSE_BOOL(BreakStringLiterals);
CHECK_PARSE_BOOL(CompactNamespaces);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 253e50437c23a9b..a9fddc3275aed99 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -11224,6 +11224,236 @@ TEST_F(FormatTest, WrapsTemplateDeclarationsWithComments) {
Style);
}
+TEST_F(FormatTest, BreakBeforeTemplateCloser) {
+ auto Style = getLLVMStyle();
+ // Begin with tests covering the case where there is no constraint on the
+ // column limit.
+ Style.ColumnLimit = 0;
+ Style.BreakBeforeTemplateCloser = true;
+ // BreakBeforeTemplateCloser should NOT force template declarations onto
+ // multiple lines.
+ verifyFormat("template <typename Foo>\n"
+ "void foo() {}",
+ Style);
+ verifyFormat("template <typename Foo, typename Bar>\n"
+ "void foo() {}",
+ Style);
+ // It should add a line break before > if not already present:
+ verifyFormat("template <\n"
+ " typename Foo\n"
+ ">\n"
+ "void foo() {}",
+ "template <\n"
+ " typename Foo>\n"
+ "void foo() {}",
+ Style);
+ verifyFormat("template <\n"
+ " typename Foo,\n"
+ " typename Bar\n"
+ ">\n"
+ "void foo() {}",
+ "template <\n"
+ " typename Foo,\n"
+ " typename Bar>\n"
+ "void foo() {}",
+ Style);
+ // When within an indent scope, the > should be placed accordingly:
+ verifyFormat("struct Baz {\n"
+ " template <\n"
+ " typename Foo,\n"
+ " typename Bar\n"
+ " >\n"
+ " void foo() {}\n"
+ "};",
+ "struct Baz {\n"
+ " template <\n"
+ " typename Foo,\n"
+ " typename Bar>\n"
+ " void foo() {}\n"
+ "};",
+ Style);
+
+ // Test from https://github.com/llvm/llvm-project/issues/80049:
+ verifyFormat(
+ "using type = std::remove_cv_t<\n"
+ " add_common_cv_reference<\n"
+ " std::common_type_t<std::decay_t<T0>, std::decay_t<T1>>,\n"
+ " T0,\n"
+ " T1\n"
+ " >\n"
+ ">;",
+ "using type = std::remove_cv_t<\n"
+ " add_common_cv_reference<\n"
+ " std::common_type_t<std::decay_t<T0>, std::decay_t<T1>>,\n"
+ " T0,\n"
+ " T1>>;",
+ Style);
+
+ // Test lambda goes to next line:
+ verifyFormat("void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T\n"
+ " >(T t) {\n"
+ " };\n"
+ "}",
+ "void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T>(T t){\n"
+ " };\n"
+ "}",
+ Style);
+ // With no column limit, two parameters can go on the same line:
+ verifyFormat("void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T, typename Foo\n"
+ " >(T t) {\n"
+ " };\n"
+ "}",
+ "void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T, typename Foo>(T t){\n"
+ " };\n"
+ "}",
+ Style);
+ // Or on
diff erent lines:
+ verifyFormat("void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T,\n"
+ " typename Foo\n"
+ " >(T t) {\n"
+ " };\n"
+ "}",
+ "void foo() {\n"
+ " auto lambda = []<\n"
+ " typename T,\n"
+ " typename Foo>(T t){\n"
+ " };\n"
+ "}",
+ Style);
+
+ // Test template usage goes to next line too:
+ verifyFormat("void foo() {\n"
+ " myFunc<\n"
+ " T\n"
+ " >();\n"
+ "}",
+ "void foo() {\n"
+ " myFunc<\n"
+ " T>();\n"
+ "}",
+ Style);
+
+ // Now test that it handles the cases when the column limit forces wrapping.
+ Style.ColumnLimit = 40;
+ // The typename goes on the first line if it fits:
+ verifyFormat("template <typename Fooooooooooooooooooo,\n"
+ " typename Bar>\n"
+ "void foo() {}",
+ Style);
+ verifyFormat("template <typename Foo,\n"
+ " typename Barrrrrrrrrrrrrrrrrr>\n"
+ "void foo() {}",
+ Style);
+ // Long names should be split in one step:
+ verifyFormat("template <\n"
+ " typename Foo,\n"
+ " typename Barrrrrrrrrrrrrrrrrrr\n"
+ ">\n"
+ "void foo() {}",
+ "template <typename Foo, typename Barrrrrrrrrrrrrrrrrrr>\n"
+ "void foo() {}",
+ Style);
+ verifyFormat("template <\n"
+ " typename Foooooooooooooooooooo,\n"
+ " typename Bar\n"
+ ">\n"
+ "void foo() {}",
+ "template <typename Foooooooooooooooooooo, typename Bar>\n"
+ "void foo() {}",
+ Style);
+ // Even when there is only one long name:
+ verifyFormat("template <\n"
+ " typename Foooooooooooooooooooo\n"
+ ">\n"
+ "void foo() {}",
+ "template <typename Foooooooooooooooooooo>\n"
+ "void foo() {}",
+ Style);
+ // Test lambda goes to next line if the type is looong:
+ verifyFormat("void foo() {\n"
+ " auto lambda =\n"
+ " []<\n"
+ " typename Loooooooooooooooooooooooooooooooooong\n"
+ " >(T t) {};\n"
+ " auto lambda =\n"
+ " [looooooooooooooong]<\n"
+ " typename Loooooooooooooooooooooooooooooooooong\n"
+ " >(T t) {};\n"
+ " auto lambda =\n"
+ " []<\n"
+ " typename T,\n"
+ " typename Loooooooooooooooooooooooooooooooooong\n"
+ " >(T t) {};\n"
+ // Nested:
+ " auto lambda =\n"
+ " []<\n"
+ " template <typename, typename>\n"
+ " typename Looooooooooooooooooong\n"
+ " >(T t) {};\n"
+ // Same idea, the "T" is now short rather than Looong:
+ " auto lambda =\n"
+ " []<template <typename, typename>\n"
+ " typename T>(T t) {};\n"
+ // Nested with long capture forces the style to block indent:
+ " auto lambda =\n"
+ " [loooooooooooooooooooong]<\n"
+ " template <typename, typename>\n"
+ " typename Looooooooooooooooooong\n"
+ " >(T t) {};\n"
+ // But *now* it stays block indented even when T is short:
+ " auto lambda =\n"
+ " [loooooooooooooooooooong]<\n"
+ " template <typename, typename>\n"
+ " typename T\n"
+ " >(T t) {};\n"
+ // Nested, with long name and long captures:
+ " auto lambda =\n"
+ " [loooooooooooooooooooong]<\n"
+ " template <\n"
+ " typename Foooooooooooooooo,\n"
+ " typename\n"
+ " >\n"
+ " typename T\n"
+ " >(T t) {};\n"
+ // Allow the nested template to be on the same line:
+ " auto lambda =\n"
+ " [loooooooooooooooooooong]<\n"
+ " template <typename Fooooooooo,\n"
+ " typename>\n"
+ " typename T\n"
+ " >(T t) {};\n"
+ "}",
+ Style);
+
+ // Test template usage goes to next line if the type is looong:
+ verifyFormat("void foo() {\n"
+ " myFunc<\n"
+ " Looooooooooooooooooooooooong\n"
+ " >();\n"
+ "}",
+ Style);
+ // Even a single type in the middle is enough to force it to block indent
+ // style:
+ verifyFormat("void foo() {\n"
+ " myFunc<\n"
+ " Foo, Foo, Foo,\n"
+ " Foooooooooooooooooooooooooooooo,\n"
+ " Foo, Foo, Foo, Foo\n"
+ " >();\n"
+ "}",
+ Style);
+}
+
TEST_F(FormatTest, WrapsTemplateParameters) {
FormatStyle Style = getLLVMStyle();
Style.AlignAfterOpenBracket = FormatStyle::BAS_DontAlign;
More information about the cfe-commits
mailing list