[clang] [clang-format] Add option AllowShortRecordOnASingleLine (PR #154580)

Tomáš Slanina via cfe-commits cfe-commits at lists.llvm.org
Thu Nov 27 17:35:27 PST 2025


https://github.com/itzexpoexpo updated https://github.com/llvm/llvm-project/pull/154580

>From 46f08a12830afac8db86cb0c476e57282461a39e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 20 Aug 2025 19:28:23 +0200
Subject: [PATCH 01/25] [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    | 24 +++++--
 clang/unittests/Format/ConfigParseTest.cpp  |  8 +++
 clang/unittests/Format/FormatTest.cpp       | 73 +++++++++++++++++++++
 8 files changed, 195 insertions(+), 14 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 4f81a084dd65b..50483709c4909 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2085,6 +2085,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 c7e57d47f9ed1..e99d0d0871bcb 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -987,6 +987,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 f0e9aff2fd21a..e973122b132c9 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -708,6 +708,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({}));
@@ -1121,6 +1129,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",
@@ -1673,6 +1683,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 79cfa73001e54..fda57d0508767 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5977,12 +5977,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 d31d656a63fc5..caf6e5c9fd930 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,6 +463,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 &&
@@ -506,7 +519,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->isNoneOf(tok::kw_class, tok::kw_enum,
                                           tok::kw_struct, Keywords.kw_record)) {
@@ -881,9 +895,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 19c83d3910902..2b6728ff296b9 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -949,6 +949,7 @@ static bool isIIFE(const UnwrappedLine &Line,
 
 static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
                                    const FormatToken &InitialToken,
+                                   const FormatToken &NextToken,
                                    const bool IsJavaRecord) {
   if (IsJavaRecord)
     return Style.BraceWrapping.AfterClass;
@@ -957,15 +958,20 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
   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:
@@ -3202,8 +3208,10 @@ void UnwrappedLineParser::parseNamespace() {
   if (FormatTok->is(tok::l_brace)) {
     FormatTok->setFinalizedType(TT_NamespaceLBrace);
 
-    if (ShouldBreakBeforeBrace(Style, InitialToken, /*IsJavaRecord=*/false))
+    if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+                               /*IsJavaRecord=*/false)) {
       addUnwrappedLine();
+    }
 
     unsigned AddLevels =
         Style.NamespaceIndentation == FormatStyle::NI_All ||
@@ -3857,7 +3865,8 @@ bool UnwrappedLineParser::parseEnum() {
   }
 
   if (!Style.AllowShortEnumsOnASingleLine &&
-      ShouldBreakBeforeBrace(Style, InitialToken, /*IsJavaRecord=*/false)) {
+      ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+                             /*IsJavaRecord=*/false)) {
     addUnwrappedLine();
   }
   // Parse enum body.
@@ -4152,8 +4161,11 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
     if (ParseAsExpr) {
       parseChildBlock();
     } else {
-      if (ShouldBreakBeforeBrace(Style, InitialToken, IsJavaRecord))
+      if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+          ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+                                 IsJavaRecord)) {
         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 fec1c48c448d2..729e96f89a03c 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,6 +668,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 5a5d77075bb3a..89abe5dae7075 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8655,6 +8655,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.
@@ -15357,6 +15370,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 3e4fdc103802204f62d81ae7520b5dfbe9f29587 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 20 Aug 2025 23:54:07 +0200
Subject: [PATCH 02/25] 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 e973122b132c9..187bec0041910 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1129,12 +1129,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 caf6e5c9fd930..73d903558b2ec 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,18 +463,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 f161f3c76d1ecaf96e504aa1a49dc2a72a6344e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:24:59 +0200
Subject: [PATCH 03/25] 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 e99d0d0871bcb..7127cd4eb36e4 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1006,7 +1006,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 187bec0041910..8952c6bcfdbbd 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -712,7 +712,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 73d903558b2ec..52b905a31377b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -465,7 +465,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 2b6728ff296b9..4367ed80635f2 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4161,7 +4161,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(),
                                  IsJavaRecord)) {
         addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 729e96f89a03c..bf74434cf4b55 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -671,8 +671,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 89abe5dae7075..2481ccbd3423e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15418,7 +15418,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 fe76a5fb48711b06ec8d899e3d87f6ae1133fa4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:54:21 +0200
Subject: [PATCH 04/25] 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 7127cd4eb36e4..800fe5c922d04 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1011,7 +1011,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 8952c6bcfdbbd..c17348fe7e07a 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1133,8 +1133,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",
@@ -1683,7 +1683,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 fda57d0508767..efd2a35a79bcd 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5985,7 +5985,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 52b905a31377b..c1b433c06bdaf 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -464,7 +464,7 @@ class LineJoiner {
     }
 
     const bool MergeShortRecord = [this, &NextLine]() {
-      switch (Style.AllowShortRecordsOnASingleLine) {
+      switch (Style.AllowShortRecordOnASingleLine) {
       case FormatStyle::SRS_Always:
         return true;
       case FormatStyle::SRS_Empty:
@@ -895,7 +895,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 4367ed80635f2..4f483eb533240 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:
@@ -4161,7 +4161,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(),
                                  IsJavaRecord)) {
         addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index bf74434cf4b55..3afaba3359e3b 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,13 +668,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 2481ccbd3423e..cca4d00152fde 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15378,7 +15378,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"
@@ -15398,7 +15398,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"
@@ -15418,7 +15418,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 baa82ac6d0e1f7144a73bc01339b153e44ca4184 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:56:58 +0200
Subject: [PATCH 05/25] 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 50483709c4909..485f2ee9f4451 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2085,9 +2085,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.
 
@@ -2107,7 +2107,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 20ca8e7665f72bc738de23664fdadfd51e6cc7ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 25 Aug 2025 00:38:53 +0200
Subject: [PATCH 06/25] 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 485f2ee9f4451..3896ce095bb3e 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2087,7 +2087,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 800fe5c922d04..a3790bd9e481e 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -987,8 +987,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,
@@ -1011,6 +1011,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 3a3314290a38c0a398a3fda42d5f539595d10e7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Tue, 26 Aug 2025 23:54:53 +0200
Subject: [PATCH 07/25] 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 3896ce095bb3e..eb655a4b0b54a 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2088,7 +2088,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 a3790bd9e481e..64430d4158abe 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1009,7 +1009,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 c1b433c06bdaf..a34724dc751cd 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -472,7 +472,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 4f483eb533240..f355d5cb0c6af 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 cca4d00152fde..f7fbc095c927f 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15398,6 +15398,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"
@@ -15418,16 +15429,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 fe2922e26caaeef5a0089d68db58f7b1ff1a0bc8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sat, 30 Aug 2025 17:41:51 +0200
Subject: [PATCH 08/25] 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 |  42 ++++----
 clang/lib/Format/UnwrappedLineParser.cpp    |   4 +-
 clang/unittests/Format/ConfigParseTest.cpp  |   8 +-
 clang/unittests/Format/FormatTest.cpp       | 114 ++++++++++++++++++--
 7 files changed, 143 insertions(+), 35 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index eb655a4b0b54a..ff6ef2c26176d 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2096,6 +2096,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 64430d4158abe..3ad8301342fb6 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -992,6 +992,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 c17348fe7e07a..750c642ae0db4 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -711,6 +711,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);
   }
@@ -1683,7 +1684,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 a34724dc751cd..4d7c703faab57 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,17 +463,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 &&
@@ -503,6 +492,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.
@@ -511,14 +512,15 @@ class LineJoiner {
       } else if (TheLine->Last->is(TT_CompoundRequirementLBrace)) {
         ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine;
       } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
-                                        TT_RecordLBrace)) {
-        // 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;
+                                        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->isNoneOf(tok::kw_class, tok::kw_enum,
                                           tok::kw_struct, Keywords.kw_record)) {
@@ -895,7 +897,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 f355d5cb0c6af..8d36169012f11 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 3afaba3359e3b..c395e6f4f29f8 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,13 +668,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 f7fbc095c927f..ebeed67a75a03 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15370,14 +15370,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"
@@ -15398,16 +15471,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;
 
@@ -15429,7 +15511,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 aeda7b8975d1f992b475e75386275ab37cde129b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Tue, 2 Sep 2025 21:11:56 +0200
Subject: [PATCH 09/25] Fix incorrect handling of left brace wrapping

---
 clang/lib/Format/UnwrappedLineFormatter.cpp |  36 ++++--
 clang/lib/Format/UnwrappedLineParser.cpp    |   3 +-
 clang/unittests/Format/FormatTest.cpp       | 124 +++++++-------------
 3 files changed, 65 insertions(+), 98 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 4d7c703faab57..16cb37719436b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -266,6 +266,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) &&
@@ -492,17 +500,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;
@@ -517,9 +525,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->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -952,9 +958,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 8d36169012f11..f177a15db0df0 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4162,8 +4162,7 @@ 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(),
                                  IsJavaRecord)) {
         addUnwrappedLine();
       }
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index ebeed67a75a03..34489f529ec0e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15370,162 +15370,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 cbbce933f7854aa6d49ec786572aede8df75db4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 3 Sep 2025 17:49:31 +0200
Subject: [PATCH 10/25] 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 34489f529ec0e..493525fa5fdaf 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15371,7 +15371,7 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
 }
 
 TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
-  FormatStyle Style = getLLVMStyle();
+  auto Style = getLLVMStyle();
 
   Style.BreakBeforeBraces = FormatStyle::BS_Custom;
   Style.BraceWrapping.SplitEmptyRecord = false;
@@ -15381,7 +15381,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"
@@ -15402,25 +15404,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);
@@ -15432,24 +15435,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"
@@ -15462,29 +15467,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 addc6420c5452d930fe4458b028a08e83abcce43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 3 Sep 2025 17:59:08 +0200
Subject: [PATCH 11/25] Fixup ShouldBreakBeforeBrace

---
 clang/lib/Format/UnwrappedLineParser.cpp | 232 ++++++++++++-----------
 1 file changed, 123 insertions(+), 109 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index f177a15db0df0..a8e9288cff4ef 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -949,7 +949,7 @@ static bool isIIFE(const UnwrappedLine &Line,
 
 static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
                                    const FormatToken &InitialToken,
-                                   const FormatToken &NextToken,
+                                   const bool IsEmptyBlock,
                                    const bool IsJavaRecord) {
   if (IsJavaRecord)
     return Style.BraceWrapping.AfterClass;
@@ -958,10 +958,9 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
   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) {
@@ -3209,7 +3208,8 @@ 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),
                                /*IsJavaRecord=*/false)) {
       addUnwrappedLine();
     }
@@ -3263,7 +3263,8 @@ void UnwrappedLineParser::parseNew() {
     if (FormatTok->is(tok::l_paren)) {
       parseParens();
 
-      // If there is a class body of an anonymous class, consume that as child.
+      // If there is a class body of an anonymous class, consume that as
+      // child.
       if (FormatTok->is(tok::l_brace))
         parseChildBlock();
       return;
@@ -3317,8 +3318,8 @@ void UnwrappedLineParser::parseForOrWhileLoop(bool HasParens) {
     nextToken();
   if (HasParens && FormatTok->is(tok::l_paren)) {
     // The type is only set for Verilog basically because we were afraid to
-    // change the existing behavior for loops. See the discussion on D121756 for
-    // details.
+    // change the existing behavior for loops. See the discussion on D121756
+    // for details.
     if (Style.isVerilog())
       FormatTok->setFinalizedType(TT_ConditionLParen);
     parseParens();
@@ -3476,7 +3477,8 @@ bool UnwrappedLineParser::parseRequires(bool SeenEqual) {
     parseRequiresExpression();
     return false;
   case tok::l_paren:
-    // Clauses and expression can start with a paren, it's unclear what we have.
+    // Clauses and expression can start with a paren, it's unclear what we
+    // have.
     break;
   default:
     // All other tokens can only be a clause.
@@ -3539,8 +3541,8 @@ bool UnwrappedLineParser::parseRequires(bool SeenEqual) {
   // Now we look forward and try to check if the paren content is a parameter
   // list. The parameters can be cv-qualified and contain references or
   // pointers.
-  // So we want basically to check for TYPE NAME, but TYPE can contain all kinds
-  // of stuff: typename, const, *, &, &&, ::, identifiers.
+  // So we want basically to check for TYPE NAME, but TYPE can contain all
+  // kinds of stuff: typename, const, *, &, &&, ::, identifiers.
 
   unsigned StoredPosition = Tokens->getPosition();
   FormatToken *NextToken = Tokens->getNextToken();
@@ -3612,8 +3614,8 @@ void UnwrappedLineParser::parseRequiresClause() {
   assert(FormatTok->is(tok::kw_requires) && "'requires' expected");
 
   // If there is no previous token, we are within a requires expression,
-  // otherwise we will always have the template or function declaration in front
-  // of it.
+  // otherwise we will always have the template or function declaration in
+  // front of it.
   bool InRequiresExpression =
       !FormatTok->Previous ||
       FormatTok->Previous->is(TT_RequiresExpressionLBrace);
@@ -3658,21 +3660,21 @@ void UnwrappedLineParser::parseRequiresExpression() {
 /// This is the body of a requires clause. It returns, when the parsing is
 /// complete, or the expression is incorrect.
 void UnwrappedLineParser::parseConstraintExpression() {
-  // The special handling for lambdas is needed since tryToParseLambda() eats a
-  // token and if a requires expression is the last part of a requires clause
-  // and followed by an attribute like [[nodiscard]] the ClosesRequiresClause is
-  // not set on the correct token. Thus we need to be aware if we even expect a
-  // lambda to be possible.
-  // template <typename T> requires requires { ... } [[nodiscard]] ...;
+  // The special handling for lambdas is needed since tryToParseLambda() eats
+  // a token and if a requires expression is the last part of a requires
+  // clause and followed by an attribute like [[nodiscard]] the
+  // ClosesRequiresClause is not set on the correct token. Thus we need to be
+  // aware if we even expect a lambda to be possible. template <typename T>
+  // requires requires { ... } [[nodiscard]] ...;
   bool LambdaNextTimeAllowed = true;
 
-  // Within lambda declarations, it is permitted to put a requires clause after
-  // its template parameter list, which would place the requires clause right
-  // before the parentheses of the parameters of the lambda declaration. Thus,
-  // we track if we expect to see grouping parentheses at all.
-  // Without this check, `requires foo<T> (T t)` in the below example would be
-  // seen as the whole requires clause, accidentally eating the parameters of
-  // the lambda.
+  // Within lambda declarations, it is permitted to put a requires clause
+  // after its template parameter list, which would place the requires clause
+  // right before the parentheses of the parameters of the lambda declaration.
+  // Thus, we track if we expect to see grouping parentheses at all. Without
+  // this check, `requires foo<T> (T t)` in the below example would be seen as
+  // the whole requires clause, accidentally eating the parameters of the
+  // lambda.
   // [&]<typename T> requires foo<T> (T t) { ... };
   bool TopLevelParensAllowed = true;
 
@@ -3805,9 +3807,9 @@ bool UnwrappedLineParser::parseEnum() {
   if (FormatTok->is(tok::kw_enum))
     nextToken();
 
-  // In TypeScript, "enum" can also be used as property name, e.g. in interface
-  // declarations. An "enum" keyword followed by a colon would be a syntax
-  // error and thus assume it is just an identifier.
+  // In TypeScript, "enum" can also be used as property name, e.g. in
+  // interface declarations. An "enum" keyword followed by a colon would be a
+  // syntax error and thus assume it is just an identifier.
   if (Style.isJavaScript() && FormatTok->isOneOf(tok::colon, tok::question))
     return false;
 
@@ -3866,7 +3868,8 @@ bool UnwrappedLineParser::parseEnum() {
   }
 
   if (!Style.AllowShortEnumsOnASingleLine &&
-      ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+      ShouldBreakBeforeBrace(Style, InitialToken,
+                             Tokens->peekNextToken()->is(tok::r_brace),
                              /*IsJavaRecord=*/false)) {
     addUnwrappedLine();
   }
@@ -4117,8 +4120,8 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
         FormatToken *Previous = FormatTok->Previous;
         if (!Previous || (Previous->isNot(tok::r_paren) &&
                           !Previous->isTypeOrIdentifier(LangOpts))) {
-          // Don't try parsing a lambda if we had a closing parenthesis before,
-          // it was probably a pointer to an array: int (*)[].
+          // Don't try parsing a lambda if we had a closing parenthesis
+          // before, it was probably a pointer to an array: int (*)[].
           if (!tryToParseLambda())
             continue;
         } else {
@@ -4162,18 +4165,20 @@ 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),
                                  IsJavaRecord)) {
         addUnwrappedLine();
       }
 
       unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
-      parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
+      parseBlock(/*MustBeDeclaration=*/true, AddLevels,
+                 /*MunchSemi=*/false);
     }
     setPreviousRBraceType(ClosingBraceType);
   }
-  // There is no addUnwrappedLine() here so that we fall through to parsing a
-  // structural element afterwards. Thus, in "class A {} n, m;",
+  // There is no addUnwrappedLine() here so that we fall through to
+  // parsing a structural element afterwards. Thus, in "class A {} n, m;",
   // "} n, m;" will end up in one unwrapped line.
 }
 
@@ -4261,8 +4266,8 @@ void UnwrappedLineParser::parseObjCInterfaceOrImplementation() {
     parseBlock(/*MustBeDeclaration=*/true);
   }
 
-  // With instance variables, this puts '}' on its own line.  Without instance
-  // variables, this ends the @interface line.
+  // With instance variables, this puts '}' on its own line.  Without
+  // instance variables, this ends the @interface line.
   addUnwrappedLine();
 
   parseObjCUntilAtEnd();
@@ -4300,7 +4305,8 @@ bool UnwrappedLineParser::parseObjCProtocol() {
   nextToken();
 
   if (FormatTok->is(tok::l_paren)) {
-    // The expression form of @protocol, e.g. "Protocol* p = @protocol(foo);".
+    // The expression form of @protocol, e.g. "Protocol* p =
+    // @protocol(foo);".
     return false;
   }
 
@@ -4335,9 +4341,9 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
   if (FormatTok->is(tok::kw_default))
     nextToken();
 
-  // Consume "async function", "function" and "default function", so that these
-  // get parsed as free-standing JS functions, i.e. do not require a trailing
-  // semicolon.
+  // Consume "async function", "function" and "default function", so that
+  // these get parsed as free-standing JS functions, i.e. do not require a
+  // trailing semicolon.
   if (FormatTok->is(Keywords.kw_async))
     nextToken();
   if (FormatTok->is(Keywords.kw_function)) {
@@ -4345,10 +4351,10 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
     return;
   }
 
-  // For imports, `export *`, `export {...}`, consume the rest of the line up
-  // to the terminating `;`. For everything else, just return and continue
-  // parsing the structural element, i.e. the declaration or expression for
-  // `export default`.
+  // For imports, `export *`, `export {...}`, consume the rest of the line
+  // up to the terminating `;`. For everything else, just return and
+  // continue parsing the structural element, i.e. the declaration or
+  // expression for `export default`.
   if (!IsImport && FormatTok->isNoneOf(tok::l_brace, tok::star) &&
       !FormatTok->isStringLiteral() &&
       !(FormatTok->is(Keywords.kw_type) &&
@@ -4360,8 +4366,8 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
     if (FormatTok->is(tok::semi))
       return;
     if (Line->Tokens.empty()) {
-      // Common issue: Automatic Semicolon Insertion wrapped the line, so the
-      // import statement should terminate.
+      // Common issue: Automatic Semicolon Insertion wrapped the line, so
+      // the import statement should terminate.
       return;
     }
     if (FormatTok->is(tok::l_brace)) {
@@ -4540,11 +4546,11 @@ void UnwrappedLineParser::parseVerilogTable() {
 }
 
 void UnwrappedLineParser::parseVerilogCaseLabel() {
-  // The label will get unindented in AnnotatingParser. If there are no leading
-  // spaces, indent the rest here so that things inside the block will be
-  // indented relative to things outside. We don't use parseLabel because we
-  // don't know whether this colon is a label or a ternary expression at this
-  // point.
+  // The label will get unindented in AnnotatingParser. If there are no
+  // leading spaces, indent the rest here so that things inside the block
+  // will be indented relative to things outside. We don't use parseLabel
+  // because we don't know whether this colon is a label or a ternary
+  // expression at this point.
   auto OrigLevel = Line->Level;
   auto FirstLine = CurrentLines->size();
   if (Line->Level == 0 || (Line->InPPDirective && Line->Level <= 1))
@@ -4552,8 +4558,8 @@ void UnwrappedLineParser::parseVerilogCaseLabel() {
   else if (!Style.IndentCaseBlocks && Keywords.isVerilogBegin(*FormatTok))
     --Line->Level;
   parseStructuralElement();
-  // Restore the indentation in both the new line and the line that has the
-  // label.
+  // Restore the indentation in both the new line and the line that has
+  // the label.
   if (CurrentLines->size() > FirstLine)
     (*CurrentLines)[FirstLine].Level = OrigLevel;
   Line->Level = OrigLevel;
@@ -4598,24 +4604,24 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
   });
 
   // If this line closes a block when in Whitesmiths mode, remember that
-  // information so that the level can be decreased after the line is added.
-  // This has to happen after the addition of the line since the line itself
-  // needs to be indented.
+  // information so that the level can be decreased after the line is
+  // added. This has to happen after the addition of the line since the
+  // line itself needs to be indented.
   bool ClosesWhitesmithsBlock =
       Line->MatchingOpeningBlockLineIndex != UnwrappedLine::kInvalidIndex &&
       Style.BreakBeforeBraces == FormatStyle::BS_Whitesmiths;
 
   // If the current line was expanded from a macro call, we use it to
-  // reconstruct an unwrapped line from the structure of the expanded unwrapped
-  // line and the unexpanded token stream.
+  // reconstruct an unwrapped line from the structure of the expanded
+  // unwrapped line and the unexpanded token stream.
   if (!parsingPPDirective() && !InExpansion && containsExpansion(*Line)) {
     if (!Reconstruct)
       Reconstruct.emplace(Line->Level, Unexpanded);
     Reconstruct->addLine(*Line);
 
     // While the reconstructed unexpanded lines are stored in the normal
-    // flow of lines, the expanded lines are stored on the side to be analyzed
-    // in an extra step.
+    // flow of lines, the expanded lines are stored on the side to be
+    // analyzed in an extra step.
     CurrentExpandedLines.push_back(std::move(*Line));
 
     if (Reconstruct->finished()) {
@@ -4633,8 +4639,9 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
       Reconstruct.reset();
     }
   } else {
-    // At the top level we only get here when no unexpansion is going on, or
-    // when conditional formatting led to unfinished macro reconstructions.
+    // At the top level we only get here when no unexpansion is going on,
+    // or when conditional formatting led to unfinished macro
+    // reconstructions.
     assert(!Reconstruct || (CurrentLines != &Lines) || !PPStack.empty());
     CurrentLines->push_back(std::move(*Line));
   }
@@ -4652,7 +4659,8 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
         std::make_move_iterator(PreprocessorDirectives.end()));
     PreprocessorDirectives.clear();
   }
-  // Disconnect the current token from the last token on the previous line.
+  // Disconnect the current token from the last token on the previous
+  // line.
   FormatTok->Previous = nullptr;
 }
 
@@ -4663,8 +4671,8 @@ bool UnwrappedLineParser::isOnNewLine(const FormatToken &FormatTok) {
          FormatTok.NewlinesBefore > 0;
 }
 
-// Checks if \p FormatTok is a line comment that continues the line comment
-// section on \p Line.
+// Checks if \p FormatTok is a line comment that continues the line
+// comment section on \p Line.
 static bool
 continuesLineCommentSection(const FormatToken &FormatTok,
                             const UnwrappedLine &Line, const FormatStyle &Style,
@@ -4680,20 +4688,21 @@ continuesLineCommentSection(const FormatToken &FormatTok,
   if (CommentPragmasRegex.match(IndentContent))
     return false;
 
-  // If Line starts with a line comment, then FormatTok continues the comment
-  // section if its original column is greater or equal to the original start
-  // column of the line.
+  // If Line starts with a line comment, then FormatTok continues the
+  // comment section if its original column is greater or equal to the
+  // original start column of the line.
   //
-  // Define the min column token of a line as follows: if a line ends in '{' or
-  // contains a '{' followed by a line comment, then the min column token is
-  // that '{'. Otherwise, the min column token of the line is the first token of
-  // the line.
+  // Define the min column token of a line as follows: if a line ends in
+  // '{' or contains a '{' followed by a line comment, then the min column
+  // token is that '{'. Otherwise, the min column token of the line is the
+  // first token of the line.
   //
   // If Line starts with a token other than a line comment, then FormatTok
-  // continues the comment section if its original column is greater than the
-  // original start column of the min column token of the line.
+  // continues the comment section if its original column is greater than
+  // the original start column of the min column token of the line.
   //
-  // For example, the second line comment continues the first in these cases:
+  // For example, the second line comment continues the first in these
+  // cases:
   //
   // // first line
   // // second line
@@ -4748,8 +4757,8 @@ continuesLineCommentSection(const FormatToken &FormatTok,
   // };
   const FormatToken *MinColumnToken = Line.Tokens.front().Tok;
 
-  // Scan for '{//'. If found, use the column of '{' as a min column for line
-  // comment section continuation.
+  // Scan for '{//'. If found, use the column of '{' as a min column for
+  // line comment section continuation.
   const FormatToken *PreviousToken = nullptr;
   for (const UnwrappedLineNode &Node : Line.Tokens) {
     if (PreviousToken && PreviousToken->is(tok::l_brace) &&
@@ -4773,14 +4782,15 @@ continuesLineCommentSection(const FormatToken &FormatTok,
 void UnwrappedLineParser::flushComments(bool NewlineBeforeNext) {
   bool JustComments = Line->Tokens.empty();
   for (FormatToken *Tok : CommentsBeforeNextToken) {
-    // Line comments that belong to the same line comment section are put on the
-    // same line since later we might want to reflow content between them.
-    // Additional fine-grained breaking of line comment sections is controlled
-    // by the class BreakableLineCommentSection in case it is desirable to keep
-    // several line comment sections in the same unwrapped line.
+    // Line comments that belong to the same line comment section are put
+    // on the same line since later we might want to reflow content
+    // between them. Additional fine-grained breaking of line comment
+    // sections is controlled by the class BreakableLineCommentSection in
+    // case it is desirable to keep several line comment sections in the
+    // same unwrapped line.
     //
-    // FIXME: Consider putting separate line comment sections as children to the
-    // unwrapped line instead.
+    // FIXME: Consider putting separate line comment sections as children
+    // to the unwrapped line instead.
     Tok->ContinuesLineCommentSection =
         continuesLineCommentSection(*Tok, *Line, Style, CommentPragmasRegex);
     if (isOnNewLine(*Tok) && JustComments && !Tok->ContinuesLineCommentSection)
@@ -4804,12 +4814,12 @@ void UnwrappedLineParser::nextToken(int LevelDifference) {
     readTokenWithJavaScriptASI();
   FormatTok->Previous = Previous;
   if (Style.isVerilog()) {
-    // Blocks in Verilog can have `begin` and `end` instead of braces.  For
+    // Blocks in Verilog can have `begin` and `end` instead of braces. For
     // keywords like `begin`, we can't treat them the same as left braces
     // because some contexts require one of them.  For example structs use
-    // braces and if blocks use keywords, and a left brace can occur in an if
-    // statement, but it is not a block.  For keywords like `end`, we simply
-    // treat them the same as right braces.
+    // braces and if blocks use keywords, and a left brace can occur in an
+    // if statement, but it is not a block.  For keywords like `end`, we
+    // simply treat them the same as right braces.
     if (Keywords.isVerilogEnd(*FormatTok))
       FormatTok->Tok.setKind(tok::r_brace);
   }
@@ -4820,10 +4830,11 @@ void UnwrappedLineParser::distributeComments(
   // Whether or not a line comment token continues a line is controlled by
   // the method continuesLineCommentSection, with the following caveat:
   //
-  // Define a trail of Comments to be a nonempty proper postfix of Comments such
-  // that each comment line from the trail is aligned with the next token, if
-  // the next token exists. If a trail exists, the beginning of the maximal
-  // trail is marked as a start of a new comment section.
+  // Define a trail of Comments to be a nonempty proper postfix of
+  // Comments such that each comment line from the trail is aligned with
+  // the next token, if the next token exists. If a trail exists, the
+  // beginning of the maximal trail is marked as a start of a new comment
+  // section.
   //
   // For example in this code:
   //
@@ -4832,9 +4843,9 @@ void UnwrappedLineParser::distributeComments(
   //   // line 2 about b
   //   int b;
   //
-  // the two lines about b form a maximal trail, so there are two sections, the
-  // first one consisting of the single comment "// line about a" and the
-  // second one consisting of the next two comments.
+  // the two lines about b form a maximal trail, so there are two
+  // sections, the first one consisting of the single comment "// line
+  // about a" and the second one consisting of the next two comments.
   if (Comments.empty())
     return;
   bool ShouldPushCommentsInCurrentLine = true;
@@ -4895,8 +4906,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
         return Tok.HasUnescapedNewline || Tok.IsFirst;
       };
 
-      // Consider preprocessor directives preceded by block comments as first
-      // on line.
+      // Consider preprocessor directives preceded by block comments as
+      // first on line.
       if (PreviousWasComment)
         return FirstNonCommentOnLine || IsFirstOnLine(Tok);
       return IsFirstOnLine(Tok);
@@ -4908,8 +4919,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
 
     while (!Line->InPPDirective && FormatTok->is(tok::hash) &&
            FirstNonCommentOnLine) {
-      // In Verilog, the backtick is used for macro invocations. In TableGen,
-      // the single hash is used for the paste operator.
+      // In Verilog, the backtick is used for macro invocations. In
+      // TableGen, the single hash is used for the paste operator.
       const auto *Next = Tokens->peekNextToken();
       if ((Style.isVerilog() && !Keywords.isVerilogPPDirective(*Next)) ||
           (Style.isTableGen() &&
@@ -4919,17 +4930,19 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
       }
       distributeComments(Comments, FormatTok);
       Comments.clear();
-      // If there is an unfinished unwrapped line, we flush the preprocessor
-      // directives only after that unwrapped line was finished later.
+      // If there is an unfinished unwrapped line, we flush the
+      // preprocessor directives only after that unwrapped line was
+      // finished later.
       bool SwitchToPreprocessorLines = !Line->Tokens.empty();
       ScopedLineState BlockState(*this, SwitchToPreprocessorLines);
       assert((LevelDifference >= 0 ||
               static_cast<unsigned>(-LevelDifference) <= Line->Level) &&
              "LevelDifference makes Line->Level negative");
       Line->Level += LevelDifference;
-      // Comments stored before the preprocessor directive need to be output
-      // before the preprocessor directive, at the same level as the
-      // preprocessor directive, as we consider them to apply to the directive.
+      // Comments stored before the preprocessor directive need to be
+      // output before the preprocessor directive, at the same level as
+      // the preprocessor directive, as we consider them to apply to the
+      // directive.
       if (Style.IndentPPDirectives == FormatStyle::PPDIS_BeforeHash &&
           PPBranchLevel > 0) {
         Line->Level += PPBranchLevel;
@@ -4955,8 +4968,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
       FormatToken *ID = FormatTok;
       unsigned Position = Tokens->getPosition();
 
-      // To correctly parse the code, we need to replace the tokens of the macro
-      // call with its expansion.
+      // To correctly parse the code, we need to replace the tokens of the
+      // macro call with its expansion.
       auto PreCall = std::move(Line);
       Line.reset(new UnwrappedLine);
       bool OldInExpansion = InExpansion;
@@ -4985,7 +4998,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
           !Macros.hasArity(ID->TokenText, Args->size())) {
         // The macro is either
         // - object-like, but we got argumnets, or
-        // - overloaded to be both object-like and function-like, but none of
+        // - overloaded to be both object-like and function-like, but none
+        // of
         //   the function-like arities match the number of arguments.
         // Thus, expand as object-like macro.
         LLVM_DEBUG(llvm::dbgs()

>From 63048a7c5cc1cf5c0fab4b297cf0bdc5145a4219 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Thu, 4 Sep 2025 16:08:04 +0200
Subject: [PATCH 12/25] 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 efd2a35a79bcd..255fa50dd560c 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5978,6 +5978,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 16cb37719436b..438d91add39d1 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -267,9 +267,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;
     }
@@ -500,7 +501,7 @@ class LineJoiner {
                  : 0;
     }
 
-    const bool TryMergeShortRecord = [this, &NextLine]() {
+    const bool TryMergeShortRecord = [&]() {
       switch (Style.AllowShortRecordOnASingleLine) {
       case FormatStyle::SRS_Never:
         return false;
@@ -521,7 +522,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.
@@ -903,7 +904,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 493525fa5fdaf..32382007b3b41 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8655,19 +8655,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.
@@ -15370,129 +15357,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 b6af146cf90a8af87dc290e2223129c4cfd0ceae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 5 Sep 2025 17:02:45 +0200
Subject: [PATCH 13/25] 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 4cfcd37df1866..43758a11463d8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -707,6 +707,7 @@ clang-format
 - Rename ``(Binary|Decimal|Hex)MinDigits`` to ``...MinDigitsInsert`` and  add
   ``(Binary|Decimal|Hex)MaxDigitsSeparator`` suboptions to
   ``IntegerLiteralSeparator``.
+- 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 438d91add39d1..9f481c480dee7 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -501,7 +501,7 @@ class LineJoiner {
                  : 0;
     }
 
-    const bool TryMergeShortRecord = [&]() {
+    auto TryMergeShortRecord = [&]() {
       switch (Style.AllowShortRecordOnASingleLine) {
       case FormatStyle::SRS_Never:
         return false;
@@ -511,7 +511,7 @@ class LineJoiner {
       case FormatStyle::SRS_Always:
         return true;
       }
-    }() && !Style.BraceWrapping.SplitEmptyRecord;
+    };
 
     if (TheLine->Last->is(tok::l_brace)) {
       bool ShouldMerge = false;
@@ -526,7 +526,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->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -960,12 +962,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 4715a10ee13717933ea39f1bb50ebe490c375a44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 7 Sep 2025 13:21:26 +0200
Subject: [PATCH 14/25] 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 3ad8301342fb6..add6b5fdb3867 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5708,6 +5708,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 9f481c480dee7..ca0d5bc9428f6 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -501,7 +501,7 @@ class LineJoiner {
                  : 0;
     }
 
-    auto TryMergeShortRecord = [&]() {
+    auto TryMergeShortRecord = [&] {
       switch (Style.AllowShortRecordOnASingleLine) {
       case FormatStyle::SRS_Never:
         return false;
@@ -528,7 +528,7 @@ class LineJoiner {
           // correctly elsewhere.
           ShouldMerge =
               !Style.BraceWrapping.AfterClass ||
-              (TryMergeShortRecord() && !Style.BraceWrapping.SplitEmptyRecord);
+              (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord());
         }
       } else if (TheLine->InPPDirective ||
                  TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -905,7 +905,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;
         }
@@ -962,14 +966,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 2e40b13c108b7cbed1209548168acac536a3b81a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 01:28:12 +0200
Subject: [PATCH 15/25] Fix interaction between AllowShortRecord and
 AllowShortBlocks options

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 13 ++++++++-----
 clang/unittests/Format/FormatTest.cpp       |  3 +++
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index ca0d5bc9428f6..20df5c139a103 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -270,7 +270,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;
     }
@@ -279,7 +280,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))
@@ -294,9 +295,10 @@ 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,
-                              Keywords.kw_record)) {
-        return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
+                              tok::kw_extern, Keywords.kw_interface)) {
+        return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
+                       (!EmptyBlock && Style.AllowShortBlocksOnASingleLine ==
+                                           FormatStyle::SBS_Always)
                    ? tryMergeSimpleBlock(I, E, Limit)
                    : 0;
       }
@@ -910,6 +912,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 32382007b3b41..82d87ee451616 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15383,6 +15383,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 05cf2aeadc587c4a89c5ecd2bd6266d1691b90de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 10:27:25 +0200
Subject: [PATCH 16/25] 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 20df5c139a103..5e82c8d3b3bce 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -271,7 +271,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;
     }
@@ -282,9 +282,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
@@ -294,6 +292,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 82d87ee451616..997238893f86e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15383,9 +15383,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;
@@ -15421,6 +15418,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 9b93306e4af64eee4aec52eafed0e86e902b184a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 23:05:13 +0200
Subject: [PATCH 17/25] 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 5e82c8d3b3bce..2b84298f04c4d 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -270,7 +270,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 a8e9288cff4ef..9dd13f630eb29 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 01d13f1008ea2059179eced53bfe4dcd1e6c1081 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 15 Sep 2025 17:44:12 +0200
Subject: [PATCH 18/25] 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 2b84298f04c4d..67f4c08a8d020 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->isNoneOf(tok::kw_class, tok::kw_enum,
                                           tok::kw_struct, Keywords.kw_record)) {
@@ -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,

>From 49f13af929bbfaf77f671b2d899b7675ee2793ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 15 Sep 2025 18:48:00 +0200
Subject: [PATCH 19/25] Minor fixes for tryMergeRecord

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 67f4c08a8d020..6b2b5720b1dc2 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,6 +10,7 @@
 #include "FormatToken.h"
 #include "NamespaceEndCommentsFixer.h"
 #include "WhitespaceManager.h"
+#include "clang/Basic/TokenKinds.h"
 #include "llvm/Support/Debug.h"
 #include <queue>
 
@@ -586,8 +587,8 @@ class LineJoiner {
     const auto *Line = I[0];
     const auto *NextLine = I[1];
 
-    auto GetRelevantAfterOption = [&](const FormatToken *tok) {
-      switch (tok->getType()) {
+    auto GetRelevantAfterOption = [&](const FormatToken *Tok) {
+      switch (Tok->getType()) {
       case TT_StructLBrace:
         return Style.BraceWrapping.AfterStruct;
       case TT_ClassLBrace:
@@ -620,8 +621,12 @@ class LineJoiner {
     // 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) &&
+      if (I + 2 == E)
+        return 0;
+
+      const bool IsEmptyBlock = I[2]->First->is(tok::r_brace);
+
+      if (!IsEmptyBlock &&
           Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
         return tryMergeSimpleBlock(I, E, Limit);
       }

>From acc2856d3450b914adb225c76e5eaaae11546fe2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Thu, 18 Sep 2025 21:32:35 +0200
Subject: [PATCH 20/25] Fix -Wswitch and -Wlogical-op-parentheses errors

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 6b2b5720b1dc2..f60867a36fc65 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -595,6 +595,8 @@ class LineJoiner {
         return Style.BraceWrapping.AfterClass;
       case TT_UnionLBrace:
         return Style.BraceWrapping.AfterUnion;
+      default:
+        return false;
       };
     };
 
@@ -602,6 +604,8 @@ class LineJoiner {
     if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) {
       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);
@@ -643,7 +647,7 @@ class LineJoiner {
       const bool IsEmptyBlock =
           Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
 
-      if (IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord ||
+      if ((IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
           Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) {
         return tryMergeSimpleBlock(I, E, Limit);
       }

>From a60828a59cac6afa90f28e79453ecbdea9dd5c09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 19 Sep 2025 16:52:11 +0200
Subject: [PATCH 21/25] Fixup comments

---
 clang/lib/Format/Format.cpp                 |  2 +-
 clang/lib/Format/TokenAnnotator.cpp         | 13 ++++++-------
 clang/lib/Format/UnwrappedLineFormatter.cpp |  1 -
 3 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 750c642ae0db4..1c8f366f05b06 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1684,9 +1684,9 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
   LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
   LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
-  LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
   LLVMStyle.AllowShortLoopsOnASingleLine = false;
   LLVMStyle.AllowShortNamespacesOnASingleLine = false;
+  LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
   LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
   LLVMStyle.AlwaysBreakBeforeMultilineStrings = false;
   LLVMStyle.AttributeMacros.push_back("__capability");
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 255fa50dd560c..9458c396679e0 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5980,13 +5980,12 @@ 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) ||
-              (Line.startsWith(tok::kw_struct) &&
-               Style.BraceWrapping.AfterStruct) ||
-              (Line.startsWith(tok::kw_union) &&
-               Style.BraceWrapping.AfterUnion)) &&
-             Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never;
+      return Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never &&
+                 (Line.startsWith(tok::kw_class) &&
+                  Style.BraceWrapping.AfterClass) ||
+             (Line.startsWith(tok::kw_struct) &&
+              Style.BraceWrapping.AfterStruct) ||
+             (Line.startsWith(tok::kw_union) && Style.BraceWrapping.AfterUnion);
     }
   }
 
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f60867a36fc65..d3a25dc73224b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,7 +10,6 @@
 #include "FormatToken.h"
 #include "NamespaceEndCommentsFixer.h"
 #include "WhitespaceManager.h"
-#include "clang/Basic/TokenKinds.h"
 #include "llvm/Support/Debug.h"
 #include <queue>
 

>From b53f14124c802ffec1af89d41d35d20835062e25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 19 Sep 2025 16:59:52 +0200
Subject: [PATCH 22/25] Fix missing empty block check

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 3 ++-
 clang/unittests/Format/FormatTest.cpp       | 6 +++++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index d3a25dc73224b..c3dde162e2671 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -647,7 +647,8 @@ class LineJoiner {
           Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
 
       if ((IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
-          Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) {
+          (!IsEmptyBlock &&
+           Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always)) {
         return tryMergeSimpleBlock(I, E, Limit);
       }
     }
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 997238893f86e..39011181ed357 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15433,7 +15433,11 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
                "{ int i; };",
                Style);
   Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
-  verifyFormat("class foo { int i; };", Style);
+  verifyFormat("class foo\n"
+               "{\n"
+               "};\n"
+               "class foo { int i; };",
+               Style);
 }
 
 TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {

>From 8f0b1ef6ef6df14eab83f53140dcc9f35ae3c102 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sat, 20 Sep 2025 03:36:53 +0200
Subject: [PATCH 23/25] Fix linux build failing

---
 clang/lib/Format/TokenAnnotator.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 9458c396679e0..1efbff1e262ce 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5981,11 +5981,12 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
     // FIXME: Not covered by tests.
     if (Right.isNot(TT_FunctionLBrace)) {
       return Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never &&
-                 (Line.startsWith(tok::kw_class) &&
-                  Style.BraceWrapping.AfterClass) ||
-             (Line.startsWith(tok::kw_struct) &&
-              Style.BraceWrapping.AfterStruct) ||
-             (Line.startsWith(tok::kw_union) && Style.BraceWrapping.AfterUnion);
+             ((Line.startsWith(tok::kw_class) &&
+               Style.BraceWrapping.AfterClass) ||
+              (Line.startsWith(tok::kw_struct) &&
+               Style.BraceWrapping.AfterStruct) ||
+              (Line.startsWith(tok::kw_union) &&
+               Style.BraceWrapping.AfterUnion));
     }
   }
 

>From 675426ac0015aa3d708f0ae14402bbd693334d8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 21 Sep 2025 13:49:33 +0200
Subject: [PATCH 24/25] Comment fixups

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 56 +++++++++------------
 1 file changed, 24 insertions(+), 32 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index c3dde162e2671..ce8408384565e 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,6 +10,7 @@
 #include "FormatToken.h"
 #include "NamespaceEndCommentsFixer.h"
 #include "WhitespaceManager.h"
+#include "clang/Basic/TokenKinds.h"
 #include "llvm/Support/Debug.h"
 #include <queue>
 
@@ -586,35 +587,34 @@ class LineJoiner {
     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;
-      default:
-        return false;
-      };
-    };
-
     // Current line begins both record and block, brace was not wrapped.
     if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) {
+      auto ShouldWrapLBrace = [&](TokenType LBraceType) {
+        switch (LBraceType) {
+        case TT_StructLBrace:
+          return Style.BraceWrapping.AfterStruct;
+        case TT_ClassLBrace:
+          return Style.BraceWrapping.AfterClass;
+        case TT_UnionLBrace:
+          return Style.BraceWrapping.AfterUnion;
+        default:
+          return false;
+        };
+      };
+
       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;
+        default:
+          return NextLine->First->is(tok::r_brace);
         }
       };
 
       if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never &&
-          (!GetRelevantAfterOption(Line->Last) ||
+          (!ShouldWrapLBrace(Line->Last->getType()) ||
            (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()))) {
         return tryMergeSimpleBlock(I, E, Limit);
       }
@@ -622,27 +622,19 @@ class LineJoiner {
 
     // Cases where the l_brace was wrapped.
     // Current line begins record, next line block.
-    if (NextLine->First->isOneOf(TT_StructLBrace, TT_ClassLBrace,
+    if (NextLine->First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
                                  TT_UnionLBrace)) {
-      if (I + 2 == E)
+      if (I + 2 == E || I[2]->First->is(tok::r_brace) ||
+          Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) {
         return 0;
-
-      const bool IsEmptyBlock = I[2]->First->is(tok::r_brace);
-
-      if (!IsEmptyBlock &&
-          Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
-        return tryMergeSimpleBlock(I, E, Limit);
       }
-    }
-
-    if (I == AnnotatedLines.begin())
-      return 0;
 
-    const auto *PreviousLine = I[-1];
+      return tryMergeSimpleBlock(I, E, Limit);
+    }
 
     // Previous line begins record, current line block.
-    if (PreviousLine->First->isOneOf(tok::kw_struct, tok::kw_class,
-                                     tok::kw_union)) {
+    if (I != AnnotatedLines.begin() &&
+        I[-1]->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);
 

>From 014700f50098f33248c851ea8fc43d2cb19bcf45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 28 Nov 2025 02:34:45 +0100
Subject: [PATCH 25/25] Fix handling of Java/JS records and interfaces

---
 clang/lib/Format/UnwrappedLineFormatter.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index ce8408384565e..3c6aac7e335d8 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -512,6 +512,13 @@ class LineJoiner {
       } else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
                                         TT_UnionLBrace)) {
         return tryMergeRecord(I, E, Limit);
+      } else if (TheLine->Last->is(TT_RecordLBrace)) {
+        // 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);
       } else if (TheLine->InPPDirective ||
                  TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
                                           tok::kw_struct, Keywords.kw_record)) {



More information about the cfe-commits mailing list