diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index bbb912eb10e94..ce38a3a9ba1f7 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:`¶ ` + If ``true``, break before a template closing bracket (``>``) when there is + a line break after the matching opening bracket (``<``). + + .. code-block:: c++ + + true: + template + + template + + template < + typename Foo, + typename Bar + > + + false: + template + + template + + template < + typename Foo, + typename Bar> + .. _BreakBeforeTernaryOperators: **BreakBeforeTernaryOperators** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ ` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a220e57d0b322..7ef0e7f478715 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -210,6 +210,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 6f432d1d50315..fbc9291ae950d 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 + /// + /// template + /// + /// template < + /// typename Foo, + /// typename Bar + /// > + /// + /// false: + /// template + /// + /// template + /// + /// 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 6f7d213c0b559..3e51b4aab1082 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 18441e10a1249..ac354aa96f86e 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 f02bf95cfeed7..387daad934f67 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1014,6 +1014,8 @@ template <> struct MappingTraits { 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 f25332e3a5f4e..94fd7ba9c0e79 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 5bb1c00ab0bb2..0cb2a1288bfd7 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 253e50437c23a..a9fddc3275aed 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 \n" + "void foo() {}", + Style); + verifyFormat("template \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>,\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>,\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 different 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 \n" + "void foo() {}", + Style); + verifyFormat("template \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 \n" + "void foo() {}", + Style); + verifyFormat("template <\n" + " typename Foooooooooooooooooooo,\n" + " typename Bar\n" + ">\n" + "void foo() {}", + "template \n" + "void foo() {}", + Style); + // Even when there is only one long name: + verifyFormat("template <\n" + " typename Foooooooooooooooooooo\n" + ">\n" + "void foo() {}", + "template \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 \n" + " typename Looooooooooooooooooong\n" + " >(T t) {};\n" + // Same idea, the "T" is now short rather than Looong: + " auto lambda =\n" + " []