Skip to content

Conversation

itzexpoexpo
Copy link

@itzexpoexpo itzexpoexpo commented Aug 20, 2025

This patch supersedes PR #151970 by adding the option AllowShortRecordOnASingleLine that allows the following formatting:

  struct foo {};
  struct bar { int i; };
  struct baz
  {
    int i;
    int j;
    int k;
  };

This commit supersedes PR llvm#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;
  };
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-format labels Aug 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 20, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-format

Author: Tomáš Slanina (itzexpoexpo)

Changes

This patch 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;
  };

Full diff: https://github.com/llvm/llvm-project/pull/154580.diff

8 Files Affected:

  • (modified) clang/docs/ClangFormatStyleOptions.rst (+32)
  • (modified) clang/include/clang/Format/Format.h (+26)
  • (modified) clang/lib/Format/Format.cpp (+11)
  • (modified) clang/lib/Format/TokenAnnotator.cpp (+8-5)
  • (modified) clang/lib/Format/UnwrappedLineFormatter.cpp (+19-3)
  • (modified) clang/lib/Format/UnwrappedLineParser.cpp (+16-7)
  • (modified) clang/unittests/Format/ConfigParseTest.cpp (+8)
  • (modified) clang/unittests/Format/FormatTest.cpp (+73)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 02986a94a656c..537d81747a834 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2093,6 +2093,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 31582a40de866..27f4e7cd9b6a7 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -992,6 +992,32 @@ struct FormatStyle {
   /// \version 20
   bool AllowShortNamespacesOnASingleLine;
 
+  /// Different styles for merging short records
+  /// (``class``,``struct``,``union``).
+  enum ShortRecordStyle : int8_t {
+    /// Never merge records into a single line.
+    SRS_Never,
+    /// Only merge empty records.
+    /// \code
+    ///   struct foo {};
+    ///   struct bar
+    ///   {
+    ///     int i;
+    ///   };
+    /// \endcode
+    SRS_Empty,
+    /// Merge all records that fit on a single line.
+    /// \code
+    ///   struct foo {};
+    ///   struct bar { int i; };
+    /// \endcode
+    SRS_All
+  };
+
+  /// Dependent on the value, ``struct bar { int i; }`` can be put on a single
+  /// line.
+  ShortRecordStyle AllowShortRecordsOnASingleLine;
+
   /// Different ways to break after the function definition return type.
   /// This option is **deprecated** and is retained for backwards compatibility.
   enum DefinitionReturnTypeBreakingStyle : int8_t {
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 063780721423f..4f9c0dfcad722 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -660,6 +660,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({}));
@@ -1012,6 +1020,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",
@@ -1535,6 +1545,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 4801d27b1395a..50ed77313366a 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5933,12 +5933,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 0adf7ee9ed543..f07559ab96418 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -453,6 +453,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 &&
@@ -495,7 +508,8 @@ class LineJoiner {
         // elsewhere.
         ShouldMerge = !Style.BraceWrapping.AfterClass ||
                       (NextLine.First->is(tok::r_brace) &&
-                       !Style.BraceWrapping.SplitEmptyRecord);
+                       !Style.BraceWrapping.SplitEmptyRecord) ||
+                      MergeShortRecord;
       } else if (TheLine->InPPDirective ||
                  !TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
                                           tok::kw_struct)) {
@@ -869,9 +883,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 91b8fdc8a3c38..3b23502febede 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -952,20 +952,26 @@ static bool isIIFE(const UnwrappedLine &Line,
 }
 
 static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
-                                   const FormatToken &InitialToken) {
+                                   const FormatToken &InitialToken,
+                                   const FormatToken &NextToken) {
   tok::TokenKind Kind = InitialToken.Tok.getKind();
   if (InitialToken.is(TT_NamespaceMacro))
     Kind = tok::kw_namespace;
 
+  bool IsEmptyBlock = NextToken.is(tok::r_brace);
+  bool WrapRecordAllowed =
+      !(IsEmptyBlock &&
+        Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never);
+
   switch (Kind) {
   case tok::kw_namespace:
     return Style.BraceWrapping.AfterNamespace;
   case tok::kw_class:
-    return Style.BraceWrapping.AfterClass;
+    return Style.BraceWrapping.AfterClass && WrapRecordAllowed;
   case tok::kw_union:
-    return Style.BraceWrapping.AfterUnion;
+    return Style.BraceWrapping.AfterUnion && WrapRecordAllowed;
   case tok::kw_struct:
-    return Style.BraceWrapping.AfterStruct;
+    return Style.BraceWrapping.AfterStruct && WrapRecordAllowed;
   case tok::kw_enum:
     return Style.BraceWrapping.AfterEnum;
   default:
@@ -3191,7 +3197,7 @@ void UnwrappedLineParser::parseNamespace() {
   if (FormatTok->is(tok::l_brace)) {
     FormatTok->setFinalizedType(TT_NamespaceLBrace);
 
-    if (ShouldBreakBeforeBrace(Style, InitialToken))
+    if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken()))
       addUnwrappedLine();
 
     unsigned AddLevels =
@@ -3856,7 +3862,7 @@ bool UnwrappedLineParser::parseEnum() {
   }
 
   if (!Style.AllowShortEnumsOnASingleLine &&
-      ShouldBreakBeforeBrace(Style, InitialToken)) {
+      ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) {
     addUnwrappedLine();
   }
   // Parse enum body.
@@ -4151,8 +4157,11 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
     if (ParseAsExpr) {
       parseChildBlock();
     } else {
-      if (ShouldBreakBeforeBrace(Style, InitialToken))
+      if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+          ShouldBreakBeforeBrace(Style, InitialToken,
+                                 *Tokens->peekNextToken())) {
         addUnwrappedLine();
+      }
 
       unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
       parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 9de3cca71630d..338f10f8ff4b9 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -655,6 +655,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 96cc650f52a5d..e62a37a772cbd 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8624,6 +8624,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.
@@ -15615,6 +15628,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;");

@owenca owenca requested a review from mydeveloperday August 21, 2025 04:44
@owenca
Copy link
Contributor

owenca commented Aug 21, 2025

Looks like we got an inconsistency in formatting short (including empty) records. With 20.1.8 we have:

$ cat a.cc
struct foo {};
struct bar { int i; }
$ clang-format a.cc
struct foo {};
struct bar {
  int i;
}

Have you investigated why? It may be worthwhile to try fixing it first.

@itzexpoexpo
Copy link
Author

itzexpoexpo commented Aug 21, 2025

Could you please provide the used config too? Might be necessary to add some more test cases around this

I tested on the default configuration and I get the same output on both 20.1.0 and the latest commit, so I'm unsure where the inconsistency is? Using BraceWrapping to highlight new option changes:

$ clang-format --version                                        
clang-format version 20.1.0   

$ clang-format a.cpp                                                
struct foo {};                                                      
struct bar {
  int i;
};

$ clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}}"    
struct foo
{
};
struct bar
{
  int i;
};

$ bin\clang-format --version                                       
clang-format version 22.0.0git (https://github.com/itzexpoexpo/llvm-project-wrapemptyrecord.git 6659057cfc4ecebc8ed963d57cb158b3b34d209e)

$ bin\clang-format a.cpp
struct foo {};
struct bar {
  int i;
};

$ bin\clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}}"
struct foo
{
};
struct bar
{
  int i;
};

$ bin\clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}, AllowShortRecordsOnASingleLine: All }"
struct foo {};
struct bar { int i; };

@owenca
Copy link
Contributor

owenca commented Aug 21, 2025

Could you please provide the used config too? Might be necessary to add some more test cases around this

It's the default config. No .clang-format file. The inconsistency is that empty records are merged but short, non-empty ones are not, even though we don't have an AllowShortRecordOnASingleLine option.

I've just glanced at the code WRT merging short/empty records (tryFitMultipleLinesInOne(), tryMergeSimpleBlock(), etc.) and seen quite a few other issues. Let's leave them out of this patch if possible, so please ignore my question.

@itzexpoexpo
Copy link
Author

Right, I see what you mean now, it also ignores explicitly specified SplitEmptyRecords: true. For now I'll open an issue and potentially look into making a PR.

@owenca
Copy link
Contributor

owenca commented Aug 24, 2025

With this patch:

$ cat a.cc
class foo {};

class bar { int i; };
$ clang-format -style='{AllowShortRecordsOnASingleLine: Empty}' a.cc
class foo {};

class bar { int i; };
$ clang-format -style='{AllowShortRecordsOnASingleLine: Never}' a.cc
class foo {};

class bar {
  int i;
};

Expected:

  • Empty
class foo {};

class bar {
  int i;
};
  • Never
class foo {
};

class bar {
  int i;
};

Am I correct?

@itzexpoexpo
Copy link
Author

...

  • Never
class foo {
};

class bar {
  int i;
};

Am I correct?

Yes, except Never then depends on SplitEmptyRecord and is therefore also affected by #154796. If you use AfterClass: true you get the correct behavior:

$ clang-format -style="{AllowShortRecordsOnASingleLine: Empty, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
class foo {};

class bar
{
  int i;
};

$ clang-format -style="{AllowShortRecordsOnASingleLine: Never, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
class foo
{
};

class bar
{
  int i;
};

Both are currently correct unless the handling of SplitEmptyRecord is changed in some way.

@owenca owenca changed the title [clang-format] Add option AllowShortRecordsOnASingleLine [clang-format] Add option AllowShortRecordOnASingleLine Aug 24, 2025
@owenca
Copy link
Contributor

owenca commented Aug 24, 2025

...

  • Never
class foo {
};

class bar {
  int i;
};

Am I correct?

Yes, except Never then depends on SplitEmptyRecord and is therefore also affected by #154796.

The command-line option -style='{AllowShortRecordsOnASingleLine: Never}' I used in the example implies AfterClass: false, so SplitEmptyRecord doesn't matter. See #154796 (comment).

If you use AfterClass: true you get the correct behavior:

$ clang-format -style="{AllowShortRecordsOnASingleLine: Empty, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
class foo {};

SplitEmptyRecord: true, which is effective here, should override AllowShortRecordsOnASingleLine for empty records.

@itzexpoexpo
Copy link
Author

The command-line option -style='{AllowShortRecordsOnASingleLine: Never}' I used in the example implies AfterClass: false, so SplitEmptyRecord doesn't matter. See #154796 (comment).

The same code that merges the brace for SplitEmptyRecord does the same for AllowShortRecordOnASingleLine since Never is treated as "option ignored", changing it so that the behavior is overriden means with the default config you'd get:

struct foo {
};

from your comment I understood this behavior is not to be changed?

SplitEmptyRecord: true, which is effective here, should override AllowShortRecordsOnASingleLine for empty records.

Right, so is the expected behavior this:

$ clang-format -style="{AllowShortRecordsOnASingleLine: Empty, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
struct foo
{
};

i.e. should SplitEmptyRecord: true mean that AllowShortRecordOnASingleLine is effectively ignored? Just checking if I understand correctly.

@owenca
Copy link
Contributor

owenca commented Aug 25, 2025

The command-line option -style='{AllowShortRecordsOnASingleLine: Never}' I used in the example implies AfterClass: false, so SplitEmptyRecord doesn't matter. See #154796 (comment).

The same code that merges the brace for SplitEmptyRecord does the same for AllowShortRecordOnASingleLine since Never is treated as "option ignored", changing it so that the behavior is overriden means with the default config you'd get:

struct foo {
};

from your comment I understood this behavior is not to be changed?

See https://github.com/llvm/llvm-project/pull/154580/files#r2296871961. The default (i.e. the LLVM style) of AllowShortRecordOnASingleLine sould be Empty.

SplitEmptyRecord: true, which is effective here, should override AllowShortRecordsOnASingleLine for empty records.

Right, so is the expected behavior this:

$ clang-format -style="{AllowShortRecordsOnASingleLine: Empty, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
struct foo
{
};

i.e. should SplitEmptyRecord: true mean that AllowShortRecordOnASingleLine is effectively ignored? Just checking if I understand correctly.

Yep!

@owenca
Copy link
Contributor

owenca commented Aug 27, 2025

See #151428. Maybe there's a similar bug with records.

@brandb97
Copy link
Contributor

brandb97 commented Aug 27, 2025

See #151428. Maybe there's a similar bug with records.

What I have changed is something like

if (ThisLine->First->is(TT_FunctionLBrace) && ...) {
  // I add some code to merge function body into a single line
}

Would this code have any bug you guys have mentioned before?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category clang-format
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants