https://github.com/itzexpoexpo updated https://github.com/llvm/llvm-project/pull/154580
From 877d155b65a054a240490d3c7e2015673f964466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Wed, 20 Aug 2025 19:28:23 +0200 Subject: [PATCH 01/18] [clang-format] Add option AllowShortRecordsOnASingleLine This commit supersedes PR #151970 by adding the option AllowShortRecordsOnASingleLine that allows the following formatting: struct foo {}; struct bar { int i; }; struct baz { int i; int j; int k; }; --- clang/docs/ClangFormatStyleOptions.rst | 32 +++++++++ clang/include/clang/Format/Format.h | 26 ++++++++ clang/lib/Format/Format.cpp | 11 ++++ clang/lib/Format/TokenAnnotator.cpp | 13 ++-- clang/lib/Format/UnwrappedLineFormatter.cpp | 22 ++++++- clang/lib/Format/UnwrappedLineParser.cpp | 23 +++++-- clang/unittests/Format/ConfigParseTest.cpp | 8 +++ clang/unittests/Format/FormatTest.cpp | 73 +++++++++++++++++++++ 8 files changed, 193 insertions(+), 15 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 6be4d512bda6a..2b26db6b4f680 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2096,6 +2096,38 @@ the configuration (without a prefix: ``Auto``). **AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>` If ``true``, ``namespace a { class b; }`` can be put on a single line. +.. _AllowShortRecordsOnASingleLine: + +**AllowShortRecordsOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordsOnASingleLine>` + Dependent on the value, ``struct bar { int i; }`` can be put on a single + line. + + Possible values: + + * ``SRS_Never`` (in configuration: ``Never``) + Never merge records into a single line. + + * ``SRS_Empty`` (in configuration: ``Empty``) + Only merge empty records. + + .. code-block:: c++ + + struct foo {}; + struct bar + { + int i; + }; + + * ``SRS_All`` (in configuration: ``All``) + Merge all records that fit on a single line. + + .. code-block:: c++ + + struct foo {}; + struct bar { int i; }; + + + .. _AlwaysBreakAfterDefinitionReturnType: **AlwaysBreakAfterDefinitionReturnType** (``DefinitionReturnTypeBreakingStyle``) :versionbadge:`clang-format 3.7` :ref:`¶ <AlwaysBreakAfterDefinitionReturnType>` diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 03cff5f8cfb66..7a254b73ac2d3 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -992,6 +992,32 @@ struct FormatStyle { /// \version 20 bool AllowShortNamespacesOnASingleLine; + /// Different styles for merging short records + /// (``class``,``struct``,``union``). + enum ShortRecordStyle : int8_t { + /// Never merge records into a single line. + SRS_Never, + /// Only merge empty records. + /// \code + /// struct foo {}; + /// struct bar + /// { + /// int i; + /// }; + /// \endcode + SRS_Empty, + /// Merge all records that fit on a single line. + /// \code + /// struct foo {}; + /// struct bar { int i; }; + /// \endcode + SRS_All + }; + + /// Dependent on the value, ``struct bar { int i; }`` can be put on a single + /// line. + ShortRecordStyle AllowShortRecordsOnASingleLine; + /// Different ways to break after the function definition return type. /// This option is **deprecated** and is retained for backwards compatibility. enum DefinitionReturnTypeBreakingStyle : int8_t { diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index f095d2c18cfcf..19ed5029b6af4 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -680,6 +680,14 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> { } }; +template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> { + static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) { + IO.enumCase(Value, "Never", FormatStyle::SRS_Never); + IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty); + IO.enumCase(Value, "All", FormatStyle::SRS_All); + } +}; + template <> struct MappingTraits<FormatStyle::SortIncludesOptions> { static void enumInput(IO &IO, FormatStyle::SortIncludesOptions &Value) { IO.enumCase(Value, "Never", FormatStyle::SortIncludesOptions({})); @@ -1043,6 +1051,8 @@ template <> struct MappingTraits<FormatStyle> { Style.AllowShortIfStatementsOnASingleLine); IO.mapOptional("AllowShortLambdasOnASingleLine", Style.AllowShortLambdasOnASingleLine); + IO.mapOptional("AllowShortRecordsOnASingleLine", + Style.AllowShortRecordsOnASingleLine); IO.mapOptional("AllowShortLoopsOnASingleLine", Style.AllowShortLoopsOnASingleLine); IO.mapOptional("AllowShortNamespacesOnASingleLine", @@ -1574,6 +1584,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never; LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All; + LLVMStyle.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; LLVMStyle.AllowShortLoopsOnASingleLine = false; LLVMStyle.AllowShortNamespacesOnASingleLine = false; LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index bbb7ef2c337d6..e235bc0734270 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -5949,12 +5949,15 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, return true; } - // Don't attempt to interpret struct return types as structs. + // Don't attempt to interpret record return types as records. if (Right.isNot(TT_FunctionLBrace)) { - return (Line.startsWith(tok::kw_class) && - Style.BraceWrapping.AfterClass) || - (Line.startsWith(tok::kw_struct) && - Style.BraceWrapping.AfterStruct); + return ((Line.startsWith(tok::kw_class) && + Style.BraceWrapping.AfterClass) || + (Line.startsWith(tok::kw_struct) && + Style.BraceWrapping.AfterStruct) || + (Line.startsWith(tok::kw_union) && + Style.BraceWrapping.AfterUnion)) && + Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never; } } diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 2a7bfd1a7dc5b..eb7d8301b621a 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -456,6 +456,19 @@ class LineJoiner { } } + auto ShouldMergeShortRecords = [this, &I, &NextLine, PreviousLine, + TheLine]() { + if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_All) + return true; + if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Empty && + NextLine.First->is(tok::r_brace)) { + return true; + } + return false; + }; + + bool MergeShortRecord = ShouldMergeShortRecords(); + // Don't merge an empty template class or struct if SplitEmptyRecords // is defined. if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord && @@ -498,7 +511,8 @@ class LineJoiner { // elsewhere. ShouldMerge = !Style.BraceWrapping.AfterClass || (NextLine.First->is(tok::r_brace) && - !Style.BraceWrapping.SplitEmptyRecord); + !Style.BraceWrapping.SplitEmptyRecord) || + MergeShortRecord; } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, tok::kw_struct)) { @@ -873,9 +887,11 @@ class LineJoiner { return 1; } else if (Limit != 0 && !Line.startsWithNamespace() && !startsExternCBlock(Line)) { - // We don't merge short records. - if (isRecordLBrace(*Line.Last)) + // Merge short records only when requested. + if (isRecordLBrace(*Line.Last) && + Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never) { return 0; + } // Check that we still have three lines and they fit into the limit. if (I + 2 == E || I[2]->Type == LT_Invalid) diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index f4bbfcf8461bc..13fb6bf8b8a74 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -952,20 +952,26 @@ static bool isIIFE(const UnwrappedLine &Line, } static bool ShouldBreakBeforeBrace(const FormatStyle &Style, - const FormatToken &InitialToken) { + const FormatToken &InitialToken, + const FormatToken &NextToken) { tok::TokenKind Kind = InitialToken.Tok.getKind(); if (InitialToken.is(TT_NamespaceMacro)) Kind = tok::kw_namespace; + bool IsEmptyBlock = NextToken.is(tok::r_brace); + bool WrapRecordAllowed = + !(IsEmptyBlock && + Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never); + switch (Kind) { case tok::kw_namespace: return Style.BraceWrapping.AfterNamespace; case tok::kw_class: - return Style.BraceWrapping.AfterClass; + return Style.BraceWrapping.AfterClass && WrapRecordAllowed; case tok::kw_union: - return Style.BraceWrapping.AfterUnion; + return Style.BraceWrapping.AfterUnion && WrapRecordAllowed; case tok::kw_struct: - return Style.BraceWrapping.AfterStruct; + return Style.BraceWrapping.AfterStruct && WrapRecordAllowed; case tok::kw_enum: return Style.BraceWrapping.AfterEnum; default: @@ -3197,7 +3203,7 @@ void UnwrappedLineParser::parseNamespace() { if (FormatTok->is(tok::l_brace)) { FormatTok->setFinalizedType(TT_NamespaceLBrace); - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) addUnwrappedLine(); unsigned AddLevels = @@ -3862,7 +3868,7 @@ bool UnwrappedLineParser::parseEnum() { } if (!Style.AllowShortEnumsOnASingleLine && - ShouldBreakBeforeBrace(Style, InitialToken)) { + ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) { addUnwrappedLine(); } // Parse enum body. @@ -4157,8 +4163,11 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (ShouldBreakBeforeBrace(Style, InitialToken)) + if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All && + ShouldBreakBeforeBrace(Style, InitialToken, + *Tokens->peekNextToken())) { addUnwrappedLine(); + } unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u; parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false); diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 7c993c0f8fd33..8fd62e422bc00 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -654,6 +654,14 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("AllowShortLambdasOnASingleLine: true", AllowShortLambdasOnASingleLine, FormatStyle::SLS_All); + Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; + CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty", + AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty); + CHECK_PARSE("AllowShortRecordsOnASingleLine: All", + AllowShortRecordsOnASingleLine, FormatStyle::SRS_All); + CHECK_PARSE("AllowShortRecordsOnASingleLine: Never", + AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never); + Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both; CHECK_PARSE("SpaceAroundPointerQualifiers: Default", SpaceAroundPointerQualifiers, FormatStyle::SAPQ_Default); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 4e9d31895998f..d16aba7ac6140 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -8575,6 +8575,19 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) { Style); } +TEST_F(FormatTest, BreakFunctionsReturningRecords) { + FormatStyle Style = getLLVMStyle(); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterFunction = true; + Style.BraceWrapping.AfterClass = false; + Style.BraceWrapping.AfterStruct = false; + Style.BraceWrapping.AfterUnion = false; + + verifyFormat("class Bar foo() {}", Style); + verifyFormat("struct Bar foo() {}", Style); + verifyFormat("union Bar foo() {}", Style); +} + TEST_F(FormatTest, DontBreakBeforeQualifiedOperator) { // Regression test for https://bugs.llvm.org/show_bug.cgi?id=40516: // Prefer keeping `::` followed by `operator` together. @@ -15277,6 +15290,66 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } +TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { + FormatStyle Style = getLLVMStyle(); + + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + Style.BraceWrapping.AfterStruct = true; + Style.BraceWrapping.AfterUnion = true; + Style.BraceWrapping.SplitEmptyRecord = false; + Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; + + verifyFormat("class foo\n{\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo\n{};", Style); + + verifyFormat("struct foo\n{\n" + " int bar;\n" + "};", + Style); + verifyFormat("struct foo\n{};", Style); + + verifyFormat("union foo\n{\n" + " int bar;\n" + "};", + Style); + verifyFormat("union foo\n{};", Style); + + Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Empty; + + verifyFormat("class foo\n{\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo\n{\n" + " int bar;\n" + "};", + Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo\n{\n" + " int bar;\n" + "};", + Style); + verifyFormat("union foo {};", Style); + + Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_All; + + verifyFormat("class foo { void bar(); };", Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo { int bar; };", Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo { int bar; };", Style); + verifyFormat("union foo {};", Style); +} + TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { // Elaborate type variable declarations. verifyFormat("struct foo a = {bar};\nint n;"); From 498c5d479c8df2625e0114b0e646ee913120744f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Wed, 20 Aug 2025 23:54:07 +0200 Subject: [PATCH 02/18] Fixup: option order, inline MergeShortRecord lambda --- clang/lib/Format/Format.cpp | 4 ++-- clang/lib/Format/UnwrappedLineFormatter.cpp | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 19ed5029b6af4..b3e103cc24545 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1051,12 +1051,12 @@ template <> struct MappingTraits<FormatStyle> { Style.AllowShortIfStatementsOnASingleLine); IO.mapOptional("AllowShortLambdasOnASingleLine", Style.AllowShortLambdasOnASingleLine); - IO.mapOptional("AllowShortRecordsOnASingleLine", - Style.AllowShortRecordsOnASingleLine); IO.mapOptional("AllowShortLoopsOnASingleLine", Style.AllowShortLoopsOnASingleLine); IO.mapOptional("AllowShortNamespacesOnASingleLine", Style.AllowShortNamespacesOnASingleLine); + IO.mapOptional("AllowShortRecordsOnASingleLine", + Style.AllowShortRecordsOnASingleLine); IO.mapOptional("AlwaysBreakAfterDefinitionReturnType", Style.AlwaysBreakAfterDefinitionReturnType); IO.mapOptional("AlwaysBreakBeforeMultilineStrings", diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index eb7d8301b621a..d74463766619a 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -456,18 +456,16 @@ class LineJoiner { } } - auto ShouldMergeShortRecords = [this, &I, &NextLine, PreviousLine, - TheLine]() { - if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_All) - return true; - if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Empty && - NextLine.First->is(tok::r_brace)) { + const bool MergeShortRecord = [this, &NextLine]() { + switch (Style.AllowShortRecordsOnASingleLine) { + case FormatStyle::SRS_All: return true; + case FormatStyle::SRS_Empty: + return NextLine.First->is(tok::r_brace); + case FormatStyle::SRS_Never: + return false; } - return false; - }; - - bool MergeShortRecord = ShouldMergeShortRecords(); + }(); // Don't merge an empty template class or struct if SplitEmptyRecords // is defined. From f5968a4443ab8573c21d2938f8c6ef771a3bceaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Sun, 24 Aug 2025 15:24:59 +0200 Subject: [PATCH 03/18] Use consistently named enum values --- clang/include/clang/Format/Format.h | 2 +- clang/lib/Format/Format.cpp | 2 +- clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +- clang/lib/Format/UnwrappedLineParser.cpp | 2 +- clang/unittests/Format/ConfigParseTest.cpp | 4 ++-- clang/unittests/Format/FormatTest.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 7a254b73ac2d3..78cc342efd478 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1011,7 +1011,7 @@ struct FormatStyle { /// struct foo {}; /// struct bar { int i; }; /// \endcode - SRS_All + SRS_Always }; /// Dependent on the value, ``struct bar { int i; }`` can be put on a single diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index b3e103cc24545..355fc71aef0bf 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -684,7 +684,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> { static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) { IO.enumCase(Value, "Never", FormatStyle::SRS_Never); IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty); - IO.enumCase(Value, "All", FormatStyle::SRS_All); + IO.enumCase(Value, "Always", FormatStyle::SRS_Always); } }; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index d74463766619a..31c0c7d41586c 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -458,7 +458,7 @@ class LineJoiner { const bool MergeShortRecord = [this, &NextLine]() { switch (Style.AllowShortRecordsOnASingleLine) { - case FormatStyle::SRS_All: + case FormatStyle::SRS_Always: return true; case FormatStyle::SRS_Empty: return NextLine.First->is(tok::r_brace); diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 13fb6bf8b8a74..cdb4a1cb6a738 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -4163,7 +4163,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All && + if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always && ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) { addUnwrappedLine(); diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 8fd62e422bc00..d0a62134f31af 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -657,8 +657,8 @@ TEST(ConfigParseTest, ParsesConfiguration) { Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty", AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty); - CHECK_PARSE("AllowShortRecordsOnASingleLine: All", - AllowShortRecordsOnASingleLine, FormatStyle::SRS_All); + CHECK_PARSE("AllowShortRecordsOnASingleLine: Always", + AllowShortRecordsOnASingleLine, FormatStyle::SRS_Always); CHECK_PARSE("AllowShortRecordsOnASingleLine: Never", AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index d16aba7ac6140..a66049244b789 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15338,7 +15338,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo {};", Style); - Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_All; + Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Always; verifyFormat("class foo { void bar(); };", Style); verifyFormat("class foo {};", Style); From bdd54a3cf3f0f87ede591caa4f602534e56c3119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Sun, 24 Aug 2025 15:54:21 +0200 Subject: [PATCH 04/18] Change option name to use singular form --- clang/include/clang/Format/Format.h | 2 +- clang/lib/Format/Format.cpp | 6 +++--- clang/lib/Format/TokenAnnotator.cpp | 2 +- clang/lib/Format/UnwrappedLineFormatter.cpp | 4 ++-- clang/lib/Format/UnwrappedLineParser.cpp | 4 ++-- clang/unittests/Format/ConfigParseTest.cpp | 14 +++++++------- clang/unittests/Format/FormatTest.cpp | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 78cc342efd478..fe438a3cecd85 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1016,7 +1016,7 @@ struct FormatStyle { /// Dependent on the value, ``struct bar { int i; }`` can be put on a single /// line. - ShortRecordStyle AllowShortRecordsOnASingleLine; + ShortRecordStyle AllowShortRecordOnASingleLine; /// Different ways to break after the function definition return type. /// This option is **deprecated** and is retained for backwards compatibility. diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 355fc71aef0bf..7cec1b5dd34c9 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1055,8 +1055,8 @@ template <> struct MappingTraits<FormatStyle> { Style.AllowShortLoopsOnASingleLine); IO.mapOptional("AllowShortNamespacesOnASingleLine", Style.AllowShortNamespacesOnASingleLine); - IO.mapOptional("AllowShortRecordsOnASingleLine", - Style.AllowShortRecordsOnASingleLine); + IO.mapOptional("AllowShortRecordOnASingleLine", + Style.AllowShortRecordOnASingleLine); IO.mapOptional("AlwaysBreakAfterDefinitionReturnType", Style.AlwaysBreakAfterDefinitionReturnType); IO.mapOptional("AlwaysBreakBeforeMultilineStrings", @@ -1584,7 +1584,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never; LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All; - LLVMStyle.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; + LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; LLVMStyle.AllowShortLoopsOnASingleLine = false; LLVMStyle.AllowShortNamespacesOnASingleLine = false; LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index e235bc0734270..ea0f73be14a7a 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -5957,7 +5957,7 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, Style.BraceWrapping.AfterStruct) || (Line.startsWith(tok::kw_union) && Style.BraceWrapping.AfterUnion)) && - Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never; + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never; } } diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 31c0c7d41586c..268e6f2e5948a 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -457,7 +457,7 @@ class LineJoiner { } const bool MergeShortRecord = [this, &NextLine]() { - switch (Style.AllowShortRecordsOnASingleLine) { + switch (Style.AllowShortRecordOnASingleLine) { case FormatStyle::SRS_Always: return true; case FormatStyle::SRS_Empty: @@ -887,7 +887,7 @@ class LineJoiner { !startsExternCBlock(Line)) { // Merge short records only when requested. if (isRecordLBrace(*Line.Last) && - Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never) { + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never) { return 0; } diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index cdb4a1cb6a738..4cc81a2cb51be 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -961,7 +961,7 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style, bool IsEmptyBlock = NextToken.is(tok::r_brace); bool WrapRecordAllowed = !(IsEmptyBlock && - Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never); + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never); switch (Kind) { case tok::kw_namespace: @@ -4163,7 +4163,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always && + if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always && ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) { addUnwrappedLine(); diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index d0a62134f31af..97e2dcda9c90c 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -654,13 +654,13 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("AllowShortLambdasOnASingleLine: true", AllowShortLambdasOnASingleLine, FormatStyle::SLS_All); - Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; - CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty", - AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty); - CHECK_PARSE("AllowShortRecordsOnASingleLine: Always", - AllowShortRecordsOnASingleLine, FormatStyle::SRS_Always); - CHECK_PARSE("AllowShortRecordsOnASingleLine: Never", - AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + CHECK_PARSE("AllowShortRecordOnASingleLine: Empty", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty); + CHECK_PARSE("AllowShortRecordOnASingleLine: Always", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Always); + CHECK_PARSE("AllowShortRecordOnASingleLine: Never", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Never); Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both; CHECK_PARSE("SpaceAroundPointerQualifiers: Default", diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index a66049244b789..39aec18a82bd7 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15298,7 +15298,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style.BraceWrapping.AfterStruct = true; Style.BraceWrapping.AfterUnion = true; Style.BraceWrapping.SplitEmptyRecord = false; - Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; verifyFormat("class foo\n{\n" " void bar();\n" @@ -15318,7 +15318,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo\n{};", Style); - Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Empty; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; verifyFormat("class foo\n{\n" " void bar();\n" @@ -15338,7 +15338,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo {};", Style); - Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Always; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; verifyFormat("class foo { void bar(); };", Style); verifyFormat("class foo {};", Style); From 4f695bd782f96788fe21b9b96e5de9d9471d5985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Sun, 24 Aug 2025 15:56:58 +0200 Subject: [PATCH 05/18] Fix docs after rename --- clang/docs/ClangFormatStyleOptions.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 2b26db6b4f680..2cddeb3c22581 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2096,9 +2096,9 @@ the configuration (without a prefix: ``Auto``). **AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>` If ``true``, ``namespace a { class b; }`` can be put on a single line. -.. _AllowShortRecordsOnASingleLine: +.. _AllowShortRecordOnASingleLine: -**AllowShortRecordsOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordsOnASingleLine>` +**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordOnASingleLine>` Dependent on the value, ``struct bar { int i; }`` can be put on a single line. @@ -2118,7 +2118,7 @@ the configuration (without a prefix: ``Auto``). int i; }; - * ``SRS_All`` (in configuration: ``All``) + * ``SRS_Always`` (in configuration: ``Always``) Merge all records that fit on a single line. .. code-block:: c++ From 584c50e3868210c8be40f86189dad114ba4ae1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 25 Aug 2025 00:38:53 +0200 Subject: [PATCH 06/18] Fixup documentation --- clang/docs/ClangFormatStyleOptions.rst | 2 +- clang/include/clang/Format/Format.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 2cddeb3c22581..03150f26c8411 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2098,7 +2098,7 @@ the configuration (without a prefix: ``Auto``). .. _AllowShortRecordOnASingleLine: -**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordOnASingleLine>` +**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>` Dependent on the value, ``struct bar { int i; }`` can be put on a single line. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index fe438a3cecd85..1735a0665566f 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -992,8 +992,8 @@ struct FormatStyle { /// \version 20 bool AllowShortNamespacesOnASingleLine; - /// Different styles for merging short records - /// (``class``,``struct``,``union``). + /// Different styles for merging short records (``class``,``struct``, and + /// ``union``). enum ShortRecordStyle : int8_t { /// Never merge records into a single line. SRS_Never, @@ -1016,6 +1016,7 @@ struct FormatStyle { /// Dependent on the value, ``struct bar { int i; }`` can be put on a single /// line. + /// \version 22 ShortRecordStyle AllowShortRecordOnASingleLine; /// Different ways to break after the function definition return type. From 606ae3d6d3c6e32d71ef02d6b6031b87c2ecc1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Tue, 26 Aug 2025 23:54:53 +0200 Subject: [PATCH 07/18] Fix SplitEmptyRecord handling, docs --- clang/docs/ClangFormatStyleOptions.rst | 2 +- clang/include/clang/Format/Format.h | 2 +- clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +- clang/lib/Format/UnwrappedLineParser.cpp | 3 ++- clang/unittests/Format/FormatTest.cpp | 25 ++++++++++++--------- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 03150f26c8411..2872d84a96d16 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2099,7 +2099,7 @@ the configuration (without a prefix: ``Auto``). .. _AllowShortRecordOnASingleLine: **AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>` - Dependent on the value, ``struct bar { int i; }`` can be put on a single + Dependent on the value, ``struct bar { int i; };`` can be put on a single line. Possible values: diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 1735a0665566f..b4734506d1667 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1014,7 +1014,7 @@ struct FormatStyle { SRS_Always }; - /// Dependent on the value, ``struct bar { int i; }`` can be put on a single + /// Dependent on the value, ``struct bar { int i; };`` can be put on a single /// line. /// \version 22 ShortRecordStyle AllowShortRecordOnASingleLine; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 268e6f2e5948a..04ab177543daf 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -465,7 +465,7 @@ class LineJoiner { case FormatStyle::SRS_Never: return false; } - }(); + }() && !Style.BraceWrapping.SplitEmptyRecord; // Don't merge an empty template class or struct if SplitEmptyRecords // is defined. diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 4cc81a2cb51be..87db07291e815 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -961,7 +961,8 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style, bool IsEmptyBlock = NextToken.is(tok::r_brace); bool WrapRecordAllowed = !(IsEmptyBlock && - Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never); + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) || + Style.BraceWrapping.SplitEmptyRecord; switch (Kind) { case tok::kw_namespace: diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 39aec18a82bd7..af9d8d994b684 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15318,6 +15318,17 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo\n{};", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + + verifyFormat("class foo { void bar(); };", Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo { int bar; };", Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo { int bar; };", Style); + verifyFormat("union foo {};", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; verifyFormat("class foo\n{\n" @@ -15338,16 +15349,10 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo {};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - - verifyFormat("class foo { void bar(); };", Style); - verifyFormat("class foo {};", Style); - - verifyFormat("struct foo { int bar; };", Style); - verifyFormat("struct foo {};", Style); - - verifyFormat("union foo { int bar; };", Style); - verifyFormat("union foo {};", Style); + Style.BraceWrapping.SplitEmptyRecord = true; + verifyFormat("class foo\n{\n}", Style); + verifyFormat("struct foo\n{\n}", Style); + verifyFormat("union foo\n{\n}", Style); } TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { From e7eb541ce03d01885618d514889888122c90f5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Sat, 30 Aug 2025 17:41:51 +0200 Subject: [PATCH 08/18] Fix behavior of Never, add EmptyIfAttached --- clang/docs/ClangFormatStyleOptions.rst | 4 + clang/include/clang/Format/Format.h | 3 + clang/lib/Format/Format.cpp | 3 +- clang/lib/Format/UnwrappedLineFormatter.cpp | 43 ++++---- clang/lib/Format/UnwrappedLineParser.cpp | 4 +- clang/unittests/Format/ConfigParseTest.cpp | 8 +- clang/unittests/Format/FormatTest.cpp | 114 ++++++++++++++++++-- 7 files changed, 144 insertions(+), 35 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 2872d84a96d16..e0881174f89b2 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2107,6 +2107,10 @@ the configuration (without a prefix: ``Auto``). * ``SRS_Never`` (in configuration: ``Never``) Never merge records into a single line. + * ``SRS_EmptyIfAttached`` (in configuration: ``EmptyIfAttached``) + Only merge empty records if the opening brace was not wrapped, + i.e. the corresponding ``BraceWrapping.After...`` option was not set. + * ``SRS_Empty`` (in configuration: ``Empty``) Only merge empty records. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index b4734506d1667..c07b1f8290992 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -997,6 +997,9 @@ struct FormatStyle { enum ShortRecordStyle : int8_t { /// Never merge records into a single line. SRS_Never, + /// Only merge empty records if the opening brace was not wrapped, + /// i.e. the corresponding ``BraceWrapping.After...`` option was not set. + SRS_EmptyIfAttached, /// Only merge empty records. /// \code /// struct foo {}; diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 7cec1b5dd34c9..e575a05490865 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -683,6 +683,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> { template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> { static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) { IO.enumCase(Value, "Never", FormatStyle::SRS_Never); + IO.enumCase(Value, "EmptyIfAttached", FormatStyle::SRS_EmptyIfAttached); IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty); IO.enumCase(Value, "Always", FormatStyle::SRS_Always); } @@ -1584,7 +1585,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never; LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All; - LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; LLVMStyle.AllowShortLoopsOnASingleLine = false; LLVMStyle.AllowShortNamespacesOnASingleLine = false; LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 04ab177543daf..593aafe3ca19a 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -456,17 +456,6 @@ class LineJoiner { } } - const bool MergeShortRecord = [this, &NextLine]() { - switch (Style.AllowShortRecordOnASingleLine) { - case FormatStyle::SRS_Always: - return true; - case FormatStyle::SRS_Empty: - return NextLine.First->is(tok::r_brace); - case FormatStyle::SRS_Never: - return false; - } - }() && !Style.BraceWrapping.SplitEmptyRecord; - // Don't merge an empty template class or struct if SplitEmptyRecords // is defined. if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord && @@ -496,6 +485,18 @@ class LineJoiner { : 0; } + const bool MergeShortRecord = [this, &NextLine]() { + switch (Style.AllowShortRecordOnASingleLine) { + case FormatStyle::SRS_Always: + return true; + case FormatStyle::SRS_EmptyIfAttached: + case FormatStyle::SRS_Empty: + return NextLine.First->is(tok::r_brace); + case FormatStyle::SRS_Never: + return false; + } + }(); + if (TheLine->Last->is(tok::l_brace)) { bool ShouldMerge = false; // Try to merge records. @@ -503,14 +504,16 @@ class LineJoiner { ShouldMerge = Style.AllowShortEnumsOnASingleLine; } else if (TheLine->Last->is(TT_CompoundRequirementLBrace)) { ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine; - } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace)) { - // NOTE: We use AfterClass (whereas AfterStruct exists) for both classes - // and structs, but it seems that wrapping is still handled correctly - // elsewhere. - ShouldMerge = !Style.BraceWrapping.AfterClass || - (NextLine.First->is(tok::r_brace) && - !Style.BraceWrapping.SplitEmptyRecord) || - MergeShortRecord; + } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace)) { + if (Style.AllowShortRecordOnASingleLine > FormatStyle::SRS_Never) { + // NOTE: We use AfterClass (whereas AfterStruct exists) for both + // classes and structs, but it seems that wrapping is still handled + // correctly elsewhere. + ShouldMerge = + !Style.BraceWrapping.AfterClass || + (MergeShortRecord && !Style.BraceWrapping.SplitEmptyRecord); + } } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, tok::kw_struct)) { @@ -887,7 +890,7 @@ class LineJoiner { !startsExternCBlock(Line)) { // Merge short records only when requested. if (isRecordLBrace(*Line.Last) && - Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never) { + Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Always) { return 0; } diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 87db07291e815..5378b2b32aaf4 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -960,8 +960,8 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style, bool IsEmptyBlock = NextToken.is(tok::r_brace); bool WrapRecordAllowed = - !(IsEmptyBlock && - Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) || + !(IsEmptyBlock && Style.AllowShortRecordOnASingleLine > + FormatStyle::SRS_EmptyIfAttached) || Style.BraceWrapping.SplitEmptyRecord; switch (Kind) { diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 97e2dcda9c90c..ec08cfab5daf9 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -654,13 +654,15 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("AllowShortLambdasOnASingleLine: true", AllowShortLambdasOnASingleLine, FormatStyle::SLS_All); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + CHECK_PARSE("AllowShortRecordOnASingleLine: Never", + AllowShortRecordOnASingleLine, FormatStyle::SRS_Never); + CHECK_PARSE("AllowShortRecordOnASingleLine: EmptyIfAttached", + AllowShortRecordOnASingleLine, FormatStyle::SRS_EmptyIfAttached); CHECK_PARSE("AllowShortRecordOnASingleLine: Empty", AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty); CHECK_PARSE("AllowShortRecordOnASingleLine: Always", AllowShortRecordOnASingleLine, FormatStyle::SRS_Always); - CHECK_PARSE("AllowShortRecordOnASingleLine: Never", - AllowShortRecordOnASingleLine, FormatStyle::SRS_Never); Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both; CHECK_PARSE("SpaceAroundPointerQualifiers: Default", diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index af9d8d994b684..9a2f122788150 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15290,14 +15290,87 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } -TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { +TEST_F(FormatTest, AllowShortRecordOnASingleLine) { FormatStyle Style = getLLVMStyle(); Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.SplitEmptyRecord = false; + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + + verifyFormat("class foo {\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo {\n};", Style); + + verifyFormat("struct foo {\n" + " int bar;\n" + "};", + Style); + verifyFormat("struct foo {\n};", Style); + + verifyFormat("union foo {\n" + " int bar;\n" + "};", + Style); + verifyFormat("union foo {\n};", Style); + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + + verifyFormat("class foo {\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo {\n" + " void bar();\n" + "};", + Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo {\n" + " void bar();\n" + "};", + Style); + verifyFormat("union foo {};", Style); + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + + verifyFormat("class foo {\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo {\n" + " int bar;\n" + "};", + Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo {\n" + " int bar;\n" + "};", + Style); + verifyFormat("union foo {};", Style); + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + + verifyFormat("class foo { void bar(); };", Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo { int bar; };", Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo { int bar; };", Style); + verifyFormat("union foo {};", Style); + Style.BraceWrapping.AfterClass = true; Style.BraceWrapping.AfterStruct = true; Style.BraceWrapping.AfterUnion = true; - Style.BraceWrapping.SplitEmptyRecord = false; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; verifyFormat("class foo\n{\n" @@ -15318,16 +15391,25 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo\n{};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo { void bar(); };", Style); - verifyFormat("class foo {};", Style); + verifyFormat("class foo\n{\n" + " void bar();\n" + "};", + Style); + verifyFormat("class foo\n{};", Style); - verifyFormat("struct foo { int bar; };", Style); - verifyFormat("struct foo {};", Style); + verifyFormat("struct foo\n{\n" + " void bar();\n" + "};", + Style); + verifyFormat("struct foo\n{};", Style); - verifyFormat("union foo { int bar; };", Style); - verifyFormat("union foo {};", Style); + verifyFormat("union foo\n{\n" + " void bar();\n" + "};", + Style); + verifyFormat("union foo\n{};", Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; @@ -15349,7 +15431,21 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) { Style); verifyFormat("union foo {};", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + + verifyFormat("class foo { void bar(); };", Style); + verifyFormat("class foo {};", Style); + + verifyFormat("struct foo { int bar; };", Style); + verifyFormat("struct foo {};", Style); + + verifyFormat("union foo { int bar; };", Style); + verifyFormat("union foo {};", Style); + + // Ensure option gets overriden by SplitEmptyRecord + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; Style.BraceWrapping.SplitEmptyRecord = true; + verifyFormat("class foo\n{\n}", Style); verifyFormat("struct foo\n{\n}", Style); verifyFormat("union foo\n{\n}", Style); From 37b37e14be9eda4307732db664599e7178f397ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Tue, 2 Sep 2025 21:11:56 +0200 Subject: [PATCH 09/18] Fix incorrect handling of left brace wrapping --- clang/lib/Format/UnwrappedLineFormatter.cpp | 36 ++++-- clang/lib/Format/UnwrappedLineParser.cpp | 5 +- clang/unittests/Format/FormatTest.cpp | 124 +++++++------------- 3 files changed, 65 insertions(+), 100 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 593aafe3ca19a..13925e2ccf287 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -260,6 +260,14 @@ class LineJoiner { } } + // Try merging record blocks that have had their left brace wrapped. + if (TheLine->First->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union) && + NextLine.First->is(tok::l_brace) && NextLine.First == NextLine.Last && + I + 2 != E && !I[2]->First->is(tok::r_brace)) { + if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) + return MergedLines; + } + const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr; // Handle empty record blocks where the brace has already been wrapped. if (PreviousLine && TheLine->Last->is(tok::l_brace) && @@ -485,17 +493,17 @@ class LineJoiner { : 0; } - const bool MergeShortRecord = [this, &NextLine]() { + const bool TryMergeShortRecord = [this, &NextLine]() { switch (Style.AllowShortRecordOnASingleLine) { - case FormatStyle::SRS_Always: - return true; + case FormatStyle::SRS_Never: + return false; case FormatStyle::SRS_EmptyIfAttached: case FormatStyle::SRS_Empty: return NextLine.First->is(tok::r_brace); - case FormatStyle::SRS_Never: - return false; + case FormatStyle::SRS_Always: + return true; } - }(); + }() && !Style.BraceWrapping.SplitEmptyRecord; if (TheLine->Last->is(tok::l_brace)) { bool ShouldMerge = false; @@ -510,9 +518,7 @@ class LineJoiner { // NOTE: We use AfterClass (whereas AfterStruct exists) for both // classes and structs, but it seems that wrapping is still handled // correctly elsewhere. - ShouldMerge = - !Style.BraceWrapping.AfterClass || - (MergeShortRecord && !Style.BraceWrapping.SplitEmptyRecord); + ShouldMerge = !Style.BraceWrapping.AfterClass || TryMergeShortRecord; } } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, @@ -945,9 +951,15 @@ class LineJoiner { return 0; Limit -= 2; unsigned MergedLines = 0; - if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never || - (I[1]->First == I[1]->Last && I + 2 != E && - I[2]->First->is(tok::r_brace))) { + + bool TryMergeBlock = + Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never; + bool TryMergeRecord = + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always; + bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E && + I[2]->First->is(tok::r_brace); + + if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) { MergedLines = tryMergeSimpleBlock(I + 1, E, Limit); // If we managed to merge the block, count the statement header, which // is on a separate line. diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 5378b2b32aaf4..e2b5072d1bf8c 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -4164,11 +4164,8 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always && - ShouldBreakBeforeBrace(Style, InitialToken, - *Tokens->peekNextToken())) { + if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) addUnwrappedLine(); - } unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u; parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 9a2f122788150..c0affdb7148c5 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15290,162 +15290,118 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } -TEST_F(FormatTest, AllowShortRecordOnASingleLine) { +TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { FormatStyle Style = getLLVMStyle(); Style.BreakBeforeBraces = FormatStyle::BS_Custom; Style.BraceWrapping.SplitEmptyRecord = false; Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo {\n" " void bar();\n" "};", Style); verifyFormat("class foo {\n};", Style); - verifyFormat("struct foo {\n" - " int bar;\n" - "};", - Style); - verifyFormat("struct foo {\n};", Style); - - verifyFormat("union foo {\n" - " int bar;\n" - "};", - Style); - verifyFormat("union foo {\n};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo {\n" " void bar();\n" "};", Style); verifyFormat("class foo {};", Style); - verifyFormat("struct foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("struct foo {};", Style); - - verifyFormat("union foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("union foo {};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo {\n" " void bar();\n" "};", Style); verifyFormat("class foo {};", Style); - verifyFormat("struct foo {\n" - " int bar;\n" - "};", - Style); - verifyFormat("struct foo {};", Style); - - verifyFormat("union foo {\n" - " int bar;\n" - "};", - Style); - verifyFormat("union foo {};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo { void bar(); };", Style); verifyFormat("class foo {};", Style); - verifyFormat("struct foo { int bar; };", Style); - verifyFormat("struct foo {};", Style); - - verifyFormat("union foo { int bar; };", Style); - verifyFormat("union foo {};", Style); - Style.BraceWrapping.AfterClass = true; Style.BraceWrapping.AfterStruct = true; Style.BraceWrapping.AfterUnion = true; Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo\n{\n" " void bar();\n" "};", Style); verifyFormat("class foo\n{};", Style); - verifyFormat("struct foo\n{\n" - " int bar;\n" - "};", - Style); - verifyFormat("struct foo\n{};", Style); - - verifyFormat("union foo\n{\n" - " int bar;\n" - "};", - Style); - verifyFormat("union foo\n{};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo\n{\n" " void bar();\n" "};", Style); verifyFormat("class foo\n{};", Style); - verifyFormat("struct foo\n{\n" + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + verifyFormat("class foo\n{\n" " void bar();\n" "};", Style); - verifyFormat("struct foo\n{};", Style); + verifyFormat("class foo {};", Style); - verifyFormat("union foo\n{\n" - " void bar();\n" - "};", - Style); - verifyFormat("union foo\n{};", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + verifyFormat("class foo { void bar(); };", Style); + verifyFormat("class foo {};", Style); +} - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; +TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) { + FormatStyle Style = getLLVMStyle(); - verifyFormat("class foo\n{\n" + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.SplitEmptyRecord = true; + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + verifyFormat("class foo {\n" " void bar();\n" "};", Style); - verifyFormat("class foo {};", Style); + verifyFormat("class foo {\n};", Style); - verifyFormat("struct foo\n{\n" - " int bar;\n" + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + verifyFormat("class foo {\n" + " void bar();\n" "};", Style); - verifyFormat("struct foo {};", Style); + verifyFormat("class foo {};", Style); - verifyFormat("union foo\n{\n" - " int bar;\n" + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + verifyFormat("class foo {\n" + " void bar();\n" "};", Style); - verifyFormat("union foo {};", Style); + verifyFormat("class foo {};", Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo { void bar(); };", Style); verifyFormat("class foo {};", Style); - verifyFormat("struct foo { int bar; };", Style); - verifyFormat("struct foo {};", Style); + Style.BraceWrapping.AfterClass = true; + Style.BraceWrapping.AfterStruct = true; + Style.BraceWrapping.AfterUnion = true; + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + verifyFormat("class foo\n{\n}", Style); + verifyFormat("struct foo\n{\n}", Style); + verifyFormat("union foo\n{\n}", Style); - verifyFormat("union foo { int bar; };", Style); - verifyFormat("union foo {};", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + verifyFormat("class foo\n{\n}", Style); + verifyFormat("struct foo\n{\n}", Style); + verifyFormat("union foo\n{\n}", Style); - // Ensure option gets overriden by SplitEmptyRecord Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - Style.BraceWrapping.SplitEmptyRecord = true; + verifyFormat("class foo\n{\n}", Style); + verifyFormat("struct foo\n{\n}", Style); + verifyFormat("union foo\n{\n}", Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; verifyFormat("class foo\n{\n}", Style); verifyFormat("struct foo\n{\n}", Style); verifyFormat("union foo\n{\n}", Style); From 04fb58e05dfd5c36560708ce003afc472cbb8761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Wed, 3 Sep 2025 17:49:31 +0200 Subject: [PATCH 10/18] Fixup test cases --- clang/unittests/Format/FormatTest.cpp | 64 +++++++++++++++------------ 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index c0affdb7148c5..fd9f7ef251362 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15291,7 +15291,7 @@ TEST_F(FormatTest, NeverMergeShortRecords) { } TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { - FormatStyle Style = getLLVMStyle(); + auto Style = getLLVMStyle(); Style.BreakBeforeBraces = FormatStyle::BS_Custom; Style.BraceWrapping.SplitEmptyRecord = false; @@ -15301,7 +15301,9 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { " void bar();\n" "};", Style); - verifyFormat("class foo {\n};", Style); + verifyFormat("class foo {\n" + "};", + Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; verifyFormat("class foo {\n" @@ -15322,25 +15324,26 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { verifyFormat("class foo {};", Style); Style.BraceWrapping.AfterClass = true; - Style.BraceWrapping.AfterStruct = true; - Style.BraceWrapping.AfterUnion = true; Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo\n{\n" + verifyFormat("class foo\n" + "{\n" " void bar();\n" "};", Style); verifyFormat("class foo\n{};", Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo\n{\n" + verifyFormat("class foo\n" + "{\n" " void bar();\n" "};", Style); verifyFormat("class foo\n{};", Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo\n{\n" + verifyFormat("class foo\n" + "{\n" " void bar();\n" "};", Style); @@ -15352,24 +15355,26 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { } TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) { - FormatStyle Style = getLLVMStyle(); + auto Style = getLLVMStyle(); - Style.BreakBeforeBraces = FormatStyle::BS_Custom; - Style.BraceWrapping.SplitEmptyRecord = true; + EXPECT_EQ(Style.BraceWrapping.SplitEmptyRecord, true); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + EXPECT_EQ(Style.AllowShortRecordOnASingleLine, + FormatStyle::SRS_EmptyIfAttached); verifyFormat("class foo {\n" " void bar();\n" "};", Style); - verifyFormat("class foo {\n};", Style); + verifyFormat("class foo {};", Style); - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; verifyFormat("class foo {\n" " void bar();\n" "};", Style); - verifyFormat("class foo {};", Style); + verifyFormat("class foo {\n" + "};", + Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; verifyFormat("class foo {\n" @@ -15382,29 +15387,32 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) { verifyFormat("class foo { void bar(); };", Style); verifyFormat("class foo {};", Style); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; Style.BraceWrapping.AfterClass = true; - Style.BraceWrapping.AfterStruct = true; - Style.BraceWrapping.AfterUnion = true; Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo\n{\n}", Style); - verifyFormat("struct foo\n{\n}", Style); - verifyFormat("union foo\n{\n}", Style); + verifyFormat("class foo\n" + "{\n" + "}", + Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo\n{\n}", Style); - verifyFormat("struct foo\n{\n}", Style); - verifyFormat("union foo\n{\n}", Style); + verifyFormat("class foo\n" + "{\n" + "}", + Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo\n{\n}", Style); - verifyFormat("struct foo\n{\n}", Style); - verifyFormat("union foo\n{\n}", Style); + verifyFormat("class foo\n" + "{\n" + "}", + Style); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo\n{\n}", Style); - verifyFormat("struct foo\n{\n}", Style); - verifyFormat("union foo\n{\n}", Style); + verifyFormat("class foo\n" + "{\n" + "}", + Style); } TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { From a5e5bff795d02387635d828ce57def0ce2772915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Wed, 3 Sep 2025 17:59:08 +0200 Subject: [PATCH 11/18] Fixup ShouldBreakBeforeBrace --- clang/lib/Format/UnwrappedLineParser.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index e2b5072d1bf8c..60c0accf0f251 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -953,15 +953,14 @@ static bool isIIFE(const UnwrappedLine &Line, static bool ShouldBreakBeforeBrace(const FormatStyle &Style, const FormatToken &InitialToken, - const FormatToken &NextToken) { + bool IsEmptyBlock) { tok::TokenKind Kind = InitialToken.Tok.getKind(); if (InitialToken.is(TT_NamespaceMacro)) Kind = tok::kw_namespace; - bool IsEmptyBlock = NextToken.is(tok::r_brace); bool WrapRecordAllowed = - !(IsEmptyBlock && Style.AllowShortRecordOnASingleLine > - FormatStyle::SRS_EmptyIfAttached) || + !IsEmptyBlock || + Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Empty || Style.BraceWrapping.SplitEmptyRecord; switch (Kind) { @@ -3204,8 +3203,10 @@ void UnwrappedLineParser::parseNamespace() { if (FormatTok->is(tok::l_brace)) { FormatTok->setFinalizedType(TT_NamespaceLBrace); - if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) + if (ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); + } unsigned AddLevels = Style.NamespaceIndentation == FormatStyle::NI_All || @@ -3869,7 +3870,8 @@ bool UnwrappedLineParser::parseEnum() { } if (!Style.AllowShortEnumsOnASingleLine && - ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) { + ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); } // Parse enum body. @@ -4164,8 +4166,10 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) { if (ParseAsExpr) { parseChildBlock(); } else { - if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) + if (ShouldBreakBeforeBrace(Style, InitialToken, + Tokens->peekNextToken()->is(tok::r_brace))) { addUnwrappedLine(); + } unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u; parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false); From b343d7253f70cd3b13a5a73d26e7e735ca29129c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Thu, 4 Sep 2025 16:08:04 +0200 Subject: [PATCH 12/18] Fixups --- clang/lib/Format/TokenAnnotator.cpp | 1 + clang/lib/Format/UnwrappedLineFormatter.cpp | 13 +- clang/unittests/Format/FormatTest.cpp | 139 +++++--------------- 3 files changed, 40 insertions(+), 113 deletions(-) diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index ea0f73be14a7a..8e066ab783f5b 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -5950,6 +5950,7 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, } // Don't attempt to interpret record return types as records. + // FIXME: Not covered by tests. if (Right.isNot(TT_FunctionLBrace)) { return ((Line.startsWith(tok::kw_class) && Style.BraceWrapping.AfterClass) || diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 13925e2ccf287..0743915e8de33 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -261,9 +261,10 @@ class LineJoiner { } // Try merging record blocks that have had their left brace wrapped. - if (TheLine->First->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union) && - NextLine.First->is(tok::l_brace) && NextLine.First == NextLine.Last && - I + 2 != E && !I[2]->First->is(tok::r_brace)) { + if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace) && + NextLine.First == NextLine.Last && I + 2 != E && + !I[2]->First->is(tok::r_brace)) { if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) return MergedLines; } @@ -493,7 +494,7 @@ class LineJoiner { : 0; } - const bool TryMergeShortRecord = [this, &NextLine]() { + const bool TryMergeShortRecord = [&]() { switch (Style.AllowShortRecordOnASingleLine) { case FormatStyle::SRS_Never: return false; @@ -514,7 +515,7 @@ class LineJoiner { ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine; } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, TT_UnionLBrace)) { - if (Style.AllowShortRecordOnASingleLine > FormatStyle::SRS_Never) { + if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) { // NOTE: We use AfterClass (whereas AfterStruct exists) for both // classes and structs, but it seems that wrapping is still handled // correctly elsewhere. @@ -896,7 +897,7 @@ class LineJoiner { !startsExternCBlock(Line)) { // Merge short records only when requested. if (isRecordLBrace(*Line.Last) && - Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Always) { + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) { return 0; } diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index fd9f7ef251362..c9d19e6b91b3f 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -8575,19 +8575,6 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) { Style); } -TEST_F(FormatTest, BreakFunctionsReturningRecords) { - FormatStyle Style = getLLVMStyle(); - Style.BreakBeforeBraces = FormatStyle::BS_Custom; - Style.BraceWrapping.AfterFunction = true; - Style.BraceWrapping.AfterClass = false; - Style.BraceWrapping.AfterStruct = false; - Style.BraceWrapping.AfterUnion = false; - - verifyFormat("class Bar foo() {}", Style); - verifyFormat("struct Bar foo() {}", Style); - verifyFormat("union Bar foo() {}", Style); -} - TEST_F(FormatTest, DontBreakBeforeQualifiedOperator) { // Regression test for https://bugs.llvm.org/show_bug.cgi?id=40516: // Prefer keeping `::` followed by `operator` together. @@ -15290,129 +15277,67 @@ TEST_F(FormatTest, NeverMergeShortRecords) { Style); } -TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) { +TEST_F(FormatTest, AllowShortRecordOnASingleLine) { auto Style = getLLVMStyle(); - - Style.BreakBeforeBraces = FormatStyle::BS_Custom; - Style.BraceWrapping.SplitEmptyRecord = false; + EXPECT_EQ(Style.AllowShortRecordOnASingleLine, + FormatStyle::SRS_EmptyIfAttached); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; verifyFormat("class foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo {\n" - "};", - Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo {};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo {\n" - " void bar();\n" + "};\n" + "class bar {\n" + " int i;\n" "};", Style); - verifyFormat("class foo {};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo { void bar(); };", Style); - verifyFormat("class foo {};", Style); - + Style.BreakBeforeBraces = FormatStyle::BS_Custom; Style.BraceWrapping.AfterClass = true; - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; verifyFormat("class foo\n" "{\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo\n{};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; - verifyFormat("class foo\n" + "};\n" + "class bar\n" "{\n" - " void bar();\n" + " int i;\n" "};", Style); - verifyFormat("class foo\n{};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + Style.BraceWrapping.SplitEmptyRecord = false; verifyFormat("class foo\n" - "{\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo {};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo { void bar(); };", Style); - verifyFormat("class foo {};", Style); -} - -TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) { - auto Style = getLLVMStyle(); - - EXPECT_EQ(Style.BraceWrapping.SplitEmptyRecord, true); - - EXPECT_EQ(Style.AllowShortRecordOnASingleLine, - FormatStyle::SRS_EmptyIfAttached); - verifyFormat("class foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo {};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo {\n" - " void bar();\n" - "};", - Style); - verifyFormat("class foo {\n" - "};", + "{};", Style); + Style = getLLVMStyle(); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo {\n" - " void bar();\n" + verifyFormat("class foo {};\n" + "class bar {\n" + " int i;\n" "};", Style); - verifyFormat("class foo {};", Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; - verifyFormat("class foo { void bar(); };", Style); - verifyFormat("class foo {};", Style); - Style.BreakBeforeBraces = FormatStyle::BS_Custom; Style.BraceWrapping.AfterClass = true; - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; - verifyFormat("class foo\n" - "{\n" - "}", - Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached; verifyFormat("class foo\n" "{\n" - "}", - Style); - - Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; - verifyFormat("class foo\n" + "};\n" + "class bar\n" "{\n" - "}", + " int i;\n" + "};", Style); + Style.BraceWrapping.SplitEmptyRecord = false; + verifyFormat("class foo {};", Style); + Style = getLLVMStyle(); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + verifyFormat("class foo {};\n" + "class bar { int i; };", + Style); + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; verifyFormat("class foo\n" "{\n" - "}", + "};\n" + "class bar { int i; };", Style); + Style.BraceWrapping.SplitEmptyRecord = false; + verifyFormat("class foo {};", Style); } TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { From 02928665649018d1c59d1c728747f6e3c45d38e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Fri, 5 Sep 2025 17:02:45 +0200 Subject: [PATCH 13/18] Update release notes, fixup UnwrappedLineFormatter --- clang/docs/ReleaseNotes.rst | 1 + clang/lib/Format/UnwrappedLineFormatter.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 51e5973098c14..042a6df4ffee2 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -472,6 +472,7 @@ clang-format - Add ``SpaceInEmptyBraces`` option and set it to ``Always`` for WebKit style. - Add ``NumericLiteralCase`` option for enforcing character case in numeric literals. +- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyIfAttached`` for LLVM style. libclang -------- diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 0743915e8de33..a7920e7bd3295 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -494,7 +494,7 @@ class LineJoiner { : 0; } - const bool TryMergeShortRecord = [&]() { + auto TryMergeShortRecord = [&]() { switch (Style.AllowShortRecordOnASingleLine) { case FormatStyle::SRS_Never: return false; @@ -504,7 +504,7 @@ class LineJoiner { case FormatStyle::SRS_Always: return true; } - }() && !Style.BraceWrapping.SplitEmptyRecord; + }; if (TheLine->Last->is(tok::l_brace)) { bool ShouldMerge = false; @@ -519,7 +519,9 @@ class LineJoiner { // NOTE: We use AfterClass (whereas AfterStruct exists) for both // classes and structs, but it seems that wrapping is still handled // correctly elsewhere. - ShouldMerge = !Style.BraceWrapping.AfterClass || TryMergeShortRecord; + ShouldMerge = + !Style.BraceWrapping.AfterClass || + (TryMergeShortRecord() && !Style.BraceWrapping.SplitEmptyRecord); } } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, @@ -953,12 +955,12 @@ class LineJoiner { Limit -= 2; unsigned MergedLines = 0; - bool TryMergeBlock = + const bool TryMergeBlock = Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never; - bool TryMergeRecord = + const bool TryMergeRecord = Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always; - bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E && - I[2]->First->is(tok::r_brace); + const bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E && + I[2]->First->is(tok::r_brace); if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) { MergedLines = tryMergeSimpleBlock(I + 1, E, Limit); From e0c028e31e1ba1c382cac7cb7250f27e0ae5fb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Sun, 7 Sep 2025 13:21:26 +0200 Subject: [PATCH 14/18] Fixup FormatStyle::operator==, misc UnwrappedLineFormatter --- clang/include/clang/Format/Format.h | 1 + clang/lib/Format/UnwrappedLineFormatter.cpp | 26 +++++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index c07b1f8290992..e2a0ed025e241 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -5492,6 +5492,7 @@ struct FormatStyle { AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine && AllowShortNamespacesOnASingleLine == R.AllowShortNamespacesOnASingleLine && + AllowShortRecordOnASingleLine == R.AllowShortRecordOnASingleLine && AlwaysBreakBeforeMultilineStrings == R.AlwaysBreakBeforeMultilineStrings && AttributeMacros == R.AttributeMacros && diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index a7920e7bd3295..9b108a1d7a7fe 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -494,7 +494,7 @@ class LineJoiner { : 0; } - auto TryMergeShortRecord = [&]() { + auto TryMergeShortRecord = [&] { switch (Style.AllowShortRecordOnASingleLine) { case FormatStyle::SRS_Never: return false; @@ -521,7 +521,7 @@ class LineJoiner { // correctly elsewhere. ShouldMerge = !Style.BraceWrapping.AfterClass || - (TryMergeShortRecord() && !Style.BraceWrapping.SplitEmptyRecord); + (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()); } } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, @@ -898,7 +898,11 @@ class LineJoiner { } else if (Limit != 0 && !Line.startsWithNamespace() && !startsExternCBlock(Line)) { // Merge short records only when requested. - if (isRecordLBrace(*Line.Last) && + if (Line.Last->isOneOf(TT_EnumLBrace, TT_RecordLBrace)) + return 0; + + if (Line.Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, + TT_UnionLBrace) && Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) { return 0; } @@ -955,14 +959,16 @@ class LineJoiner { Limit -= 2; unsigned MergedLines = 0; - const bool TryMergeBlock = - Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never; - const bool TryMergeRecord = - Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always; - const bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E && - I[2]->First->is(tok::r_brace); + auto TryMergeBlock = [&] { + if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never || + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { + return true; + } + return I[1]->First == I[1]->Last && I + 2 != E && + I[2]->First->is(tok::r_brace); + }; - if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) { + if (TryMergeBlock()) { MergedLines = tryMergeSimpleBlock(I + 1, E, Limit); // If we managed to merge the block, count the statement header, which // is on a separate line. From 8948ef2a3a40f0706331829a5770869e44dadd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 8 Sep 2025 01:28:12 +0200 Subject: [PATCH 15/18] Fix interaction between AllowShortRecord and AllowShortBlocks options --- clang/lib/Format/UnwrappedLineFormatter.cpp | 10 +++++++--- clang/unittests/Format/FormatTest.cpp | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 9b108a1d7a7fe..afccd366f5a39 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -264,7 +264,8 @@ class LineJoiner { if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace, TT_UnionLBrace) && NextLine.First == NextLine.Last && I + 2 != E && - !I[2]->First->is(tok::r_brace)) { + !I[2]->First->is(tok::r_brace) && + Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) { if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) return MergedLines; } @@ -273,7 +274,7 @@ class LineJoiner { // Handle empty record blocks where the brace has already been wrapped. if (PreviousLine && TheLine->Last->is(tok::l_brace) && TheLine->First == TheLine->Last) { - bool EmptyBlock = NextLine.First->is(tok::r_brace); + const bool EmptyBlock = NextLine.First->is(tok::r_brace); const FormatToken *Tok = PreviousLine->First; if (Tok && Tok->is(tok::comment)) @@ -289,7 +290,9 @@ class LineJoiner { Tok = Tok->getNextNonComment(); if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union, tok::kw_extern, Keywords.kw_interface)) { - return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock + return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) || + (!EmptyBlock && Style.AllowShortBlocksOnASingleLine == + FormatStyle::SBS_Always) ? tryMergeSimpleBlock(I, E, Limit) : 0; } @@ -903,6 +906,7 @@ class LineJoiner { if (Line.Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, TT_UnionLBrace) && + Line.Last != Line.First && Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) { return 0; } diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index c9d19e6b91b3f..f79d7d051cf0b 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15303,6 +15303,9 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) { verifyFormat("class foo\n" "{};", Style); + Style.BraceWrapping.SplitEmptyRecord = true; + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always; + verifyFormat("class foo\n{ int i; };", Style); Style = getLLVMStyle(); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; From a11cb9cce8cbda14849162057de8d8bbe618b897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 8 Sep 2025 10:27:25 +0200 Subject: [PATCH 16/18] Fix incorrect merge check --- clang/lib/Format/UnwrappedLineFormatter.cpp | 7 +++---- clang/unittests/Format/FormatTest.cpp | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index afccd366f5a39..874f901d8ad6b 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -265,7 +265,7 @@ class LineJoiner { TT_UnionLBrace) && NextLine.First == NextLine.Last && I + 2 != E && !I[2]->First->is(tok::r_brace) && - Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) { + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) return MergedLines; } @@ -276,9 +276,7 @@ class LineJoiner { TheLine->First == TheLine->Last) { const bool EmptyBlock = NextLine.First->is(tok::r_brace); - const FormatToken *Tok = PreviousLine->First; - if (Tok && Tok->is(tok::comment)) - Tok = Tok->getNextNonComment(); + const FormatToken *Tok = PreviousLine->getFirstNonComment(); if (Tok && Tok->getNamespaceToken()) { return !Style.BraceWrapping.SplitEmptyNamespace && EmptyBlock @@ -288,6 +286,7 @@ class LineJoiner { if (Tok && Tok->is(tok::kw_typedef)) Tok = Tok->getNextNonComment(); + if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union, tok::kw_extern, Keywords.kw_interface)) { return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) || diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index f79d7d051cf0b..25150ae86ebab 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15303,9 +15303,6 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) { verifyFormat("class foo\n" "{};", Style); - Style.BraceWrapping.SplitEmptyRecord = true; - Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always; - verifyFormat("class foo\n{ int i; };", Style); Style = getLLVMStyle(); Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; @@ -15341,6 +15338,22 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) { Style); Style.BraceWrapping.SplitEmptyRecord = false; verifyFormat("class foo {};", Style); + + Style = getLLVMStyle(); + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always; + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterClass = true; + + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never; + verifyFormat("class foo\n" + "{ int i; };", + Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty; + verifyFormat("class foo\n" + "{ int i; };", + Style); + Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always; + verifyFormat("class foo { int i; };", Style); } TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) { From 5c0251477752b90db8695e9867549610f515a1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 8 Sep 2025 23:05:13 +0200 Subject: [PATCH 17/18] Add const, change is to isNot --- clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +- clang/lib/Format/UnwrappedLineParser.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 874f901d8ad6b..4b4055a01f613 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -264,7 +264,7 @@ class LineJoiner { if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace, TT_UnionLBrace) && NextLine.First == NextLine.Last && I + 2 != E && - !I[2]->First->is(tok::r_brace) && + I[2]->First->isNot(tok::r_brace) && Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) return MergedLines; diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 60c0accf0f251..ad7258dfd5c34 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -958,7 +958,7 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style, if (InitialToken.is(TT_NamespaceMacro)) Kind = tok::kw_namespace; - bool WrapRecordAllowed = + const bool WrapRecordAllowed = !IsEmptyBlock || Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Empty || Style.BraceWrapping.SplitEmptyRecord; From 5c6ff92fff841cb5b2a9383f3b6b4d3ad88ed956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoe...@gmail.com> Date: Mon, 15 Sep 2025 17:44:12 +0200 Subject: [PATCH 18/18] Extract record merging into a separate function --- clang/lib/Format/UnwrappedLineFormatter.cpp | 111 ++++++++++++++------ 1 file changed, 79 insertions(+), 32 deletions(-) diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 2ed2c874a649b..25480cb387517 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -266,18 +266,17 @@ class LineJoiner { } } - // Try merging record blocks that have had their left brace wrapped. + // Try merging record blocks that have had their left brace wrapped into + // a single line. if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace, - TT_UnionLBrace) && - NextLine.First == NextLine.Last && I + 2 != E && - I[2]->First->isNot(tok::r_brace) && - Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { - if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit)) + TT_UnionLBrace)) { + if (unsigned MergedLines = tryMergeRecord(I, E, Limit)) return MergedLines; } const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr; - // Handle empty record blocks where the brace has already been wrapped. + + // Handle blocks where the brace has already been wrapped. if (PreviousLine && TheLine->Last->is(tok::l_brace) && TheLine->First == TheLine->Last) { const bool EmptyBlock = NextLine.First->is(tok::r_brace); @@ -293,11 +292,11 @@ class LineJoiner { if (Tok && Tok->is(tok::kw_typedef)) Tok = Tok->getNextNonComment(); - if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union, - tok::kw_extern, Keywords.kw_interface)) { - return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) || - (!EmptyBlock && Style.AllowShortBlocksOnASingleLine == - FormatStyle::SBS_Always) + if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union)) + return tryMergeRecord(I, E, Limit); + + if (Tok && Tok->isOneOf(tok::kw_extern, Keywords.kw_interface)) { + return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock ? tryMergeSimpleBlock(I, E, Limit) : 0; } @@ -502,18 +501,6 @@ class LineJoiner { : 0; } - auto TryMergeShortRecord = [&] { - switch (Style.AllowShortRecordOnASingleLine) { - case FormatStyle::SRS_Never: - return false; - case FormatStyle::SRS_EmptyIfAttached: - case FormatStyle::SRS_Empty: - return NextLine.First->is(tok::r_brace); - case FormatStyle::SRS_Always: - return true; - } - }; - if (TheLine->Last->is(tok::l_brace)) { bool ShouldMerge = false; // Try to merge records. @@ -523,14 +510,7 @@ class LineJoiner { ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine; } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace, TT_UnionLBrace)) { - if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) { - // NOTE: We use AfterClass (whereas AfterStruct exists) for both - // classes and structs, but it seems that wrapping is still handled - // correctly elsewhere. - ShouldMerge = - !Style.BraceWrapping.AfterClass || - (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()); - } + return tryMergeRecord(I, E, Limit); } else if (TheLine->InPPDirective || !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum, tok::kw_struct)) { @@ -600,6 +580,73 @@ class LineJoiner { return 0; } + unsigned tryMergeRecord(ArrayRef<AnnotatedLine *>::const_iterator I, + ArrayRef<AnnotatedLine *>::const_iterator E, + unsigned Limit) { + const auto *Line = I[0]; + const auto *NextLine = I[1]; + + auto GetRelevantAfterOption = [&](const FormatToken *tok) { + switch (tok->getType()) { + case TT_StructLBrace: + return Style.BraceWrapping.AfterStruct; + case TT_ClassLBrace: + return Style.BraceWrapping.AfterClass; + case TT_UnionLBrace: + return Style.BraceWrapping.AfterUnion; + }; + }; + + // Current line begins both record and block, brace was not wrapped. + if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) { + auto TryMergeShortRecord = [&] { + switch (Style.AllowShortRecordOnASingleLine) { + case FormatStyle::SRS_EmptyIfAttached: + case FormatStyle::SRS_Empty: + return NextLine->First->is(tok::r_brace); + case FormatStyle::SRS_Always: + return true; + } + }; + + if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never && + (!GetRelevantAfterOption(Line->Last) || + (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()))) { + return tryMergeSimpleBlock(I, E, Limit); + } + } + + // Cases where the l_brace was wrapped. + // Current line begins record, next line block. + if (NextLine->First->isOneOf(TT_StructLBrace, TT_ClassLBrace, + TT_UnionLBrace)) { + if (NextLine->First == NextLine->Last && I + 2 != E && + I[2]->First->isNot(tok::r_brace) && + Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) { + return tryMergeSimpleBlock(I, E, Limit); + } + } + + if (I == AnnotatedLines.begin()) + return 0; + + const auto *PreviousLine = I[-1]; + + // Previous line begins record, current line block. + if (PreviousLine->First->isOneOf(tok::kw_struct, tok::kw_class, + tok::kw_union)) { + const bool IsEmptyBlock = + Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace); + + if (IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord || + Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) { + return tryMergeSimpleBlock(I, E, Limit); + } + } + + return 0; + } + unsigned tryMergeSimplePPDirective(ArrayRef<AnnotatedLine *>::const_iterator I, ArrayRef<AnnotatedLine *>::const_iterator E, _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits