[clang] [clang-format] Add option AllowShortRecordOnASingleLine (PR #154580)
Tomáš Slanina via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 13 20:21:45 PDT 2026
https://github.com/itzexpoexpo updated https://github.com/llvm/llvm-project/pull/154580
>From a869078bc67741c0640a36fde9e43ebecf71134e 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/30] [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 ed4e2aaa26052..2c383127be885 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 2028c963ac306..3c466829aeb33 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -988,6 +988,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 2f67ec86b101a..99819bbe0684a 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -767,6 +767,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({}));
@@ -1180,6 +1188,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",
@@ -1734,6 +1744,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 34e81bbc97578..23a78d4670e16 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6043,12 +6043,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 8589aa83f6c55..733a209c9456e 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)) {
@@ -883,9 +897,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 f57ef1328eac7..05a1d68d5ae7a 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:
@@ -3204,8 +3210,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 ||
@@ -3859,7 +3867,8 @@ bool UnwrappedLineParser::parseEnum() {
}
if (!Style.AllowShortEnumsOnASingleLine &&
- ShouldBreakBeforeBrace(Style, InitialToken, /*IsJavaRecord=*/false)) {
+ ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ /*IsJavaRecord=*/false)) {
addUnwrappedLine();
}
// Parse enum body.
@@ -4154,8 +4163,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 f270602f32604..bff17af3ea676 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -716,6 +716,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 43633b582a8ab..d38e8a766513d 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8703,6 +8703,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.
@@ -15405,6 +15418,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 47fd5d03768f194d97a1483487efa2627c36ec0d 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/30] 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 99819bbe0684a..454ec88f1099d 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1188,12 +1188,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 733a209c9456e..036b32bbed7d6 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 4d022f264a7c7d30ac5685ab80ef9a1e5d64db01 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/30] 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 3c466829aeb33..d1190b1b207ff 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1007,7 +1007,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 454ec88f1099d..fa7836f4c640f 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -771,7 +771,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 036b32bbed7d6..ff833bafe65d4 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 05a1d68d5ae7a..3c9d371e74ee5 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4163,7 +4163,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+ if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always &&
ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
IsJavaRecord)) {
addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index bff17af3ea676..0026939aac7cd 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -719,8 +719,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 d38e8a766513d..7c2e4b21f9ef4 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15466,7 +15466,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 c48c45998ab3464c0050021c22860244650bf262 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/30] 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 d1190b1b207ff..67c5459a1031b 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1012,7 +1012,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 fa7836f4c640f..e145b5f42872a 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1192,8 +1192,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",
@@ -1744,7 +1744,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 23a78d4670e16..ffdf90ae646a2 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6051,7 +6051,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 ff833bafe65d4..3a91c698e009c 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:
@@ -897,7 +897,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 3c9d371e74ee5..47ce05d324287 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -961,7 +961,7 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
bool IsEmptyBlock = NextToken.is(tok::r_brace);
bool WrapRecordAllowed =
!(IsEmptyBlock &&
- Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never);
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never);
switch (Kind) {
case tok::kw_namespace:
@@ -4163,7 +4163,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always &&
+ if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always &&
ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
IsJavaRecord)) {
addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 0026939aac7cd..2675931a85bce 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -716,13 +716,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 7c2e4b21f9ef4..14a81cbb1fa47 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15426,7 +15426,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"
@@ -15446,7 +15446,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"
@@ -15466,7 +15466,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 b6ca4fc2a0183959f61c12c51029dcc8899e5c4e 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/30] 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 2c383127be885..38cd1e36feaa0 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 59f4fd748fe305fcefcb40f793b27d762b49aac1 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/30] 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 38cd1e36feaa0..f1051cfb6f74c 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 67c5459a1031b..552d220eaf2de 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -988,8 +988,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,
@@ -1012,6 +1012,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 21ed96fe4da609b4fd5a9f703530b87e0e3de699 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/30] 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 f1051cfb6f74c..a2519d3ed8020 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 552d220eaf2de..15197f199c981 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1010,7 +1010,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 3a91c698e009c..2639c60cb5f07 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 47ce05d324287..e113bcee2d6e7 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 14a81cbb1fa47..c1d737db43c9f 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15446,6 +15446,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"
@@ -15466,16 +15477,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 ed5a6c042ae598d70e1d2a96ac0a3ebcfa842e7f 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/30] 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 a2519d3ed8020..a11ffe8d30c5d 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 15197f199c981..c662ae5c44e4e 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -993,6 +993,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 e145b5f42872a..f273ba2738b6c 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -770,6 +770,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);
}
@@ -1744,7 +1745,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 2639c60cb5f07..ec188cc764a8d 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)) {
@@ -897,7 +899,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 e113bcee2d6e7..371a581338e24 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 2675931a85bce..4f32bbfe9d130 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -716,13 +716,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 c1d737db43c9f..07434158ce543 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15418,14 +15418,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"
@@ -15446,16 +15519,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;
@@ -15477,7 +15559,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 f365ba693cd21d9a388f5f0c8062cff2d9eb9eff 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/30] 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 ec188cc764a8d..cd84c6a4c312b 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,
@@ -954,9 +960,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 371a581338e24..2b6e9dfd56f57 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4164,8 +4164,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 07434158ce543..245e74c71bead 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15418,162 +15418,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 0ea794856e1c28dbd146bba9afc0ce9585c15161 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/30] 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 245e74c71bead..f3ae6e39ca98e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15419,7 +15419,7 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
}
TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
- FormatStyle Style = getLLVMStyle();
+ auto Style = getLLVMStyle();
Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.SplitEmptyRecord = false;
@@ -15429,7 +15429,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"
@@ -15450,25 +15452,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);
@@ -15480,24 +15483,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"
@@ -15510,29 +15515,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 815b5239e050c8826a48f8c984aa28684295a2b2 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/30] 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 2b6e9dfd56f57..63a663b71b14f 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) {
@@ -3211,7 +3210,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();
}
@@ -3265,7 +3265,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;
@@ -3319,8 +3320,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();
@@ -3478,7 +3479,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.
@@ -3541,8 +3543,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();
@@ -3614,8 +3616,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);
@@ -3660,21 +3662,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;
@@ -3807,9 +3809,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;
@@ -3868,7 +3870,8 @@ bool UnwrappedLineParser::parseEnum() {
}
if (!Style.AllowShortEnumsOnASingleLine &&
- ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ ShouldBreakBeforeBrace(Style, InitialToken,
+ Tokens->peekNextToken()->is(tok::r_brace),
/*IsJavaRecord=*/false)) {
addUnwrappedLine();
}
@@ -4119,8 +4122,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 {
@@ -4164,18 +4167,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.
}
@@ -4263,8 +4268,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();
@@ -4302,7 +4307,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;
}
@@ -4337,9 +4343,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)) {
@@ -4347,10 +4353,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) &&
@@ -4362,8 +4368,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)) {
@@ -4542,11 +4548,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))
@@ -4554,8 +4560,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;
@@ -4600,24 +4606,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()) {
@@ -4635,8 +4641,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));
}
@@ -4654,7 +4661,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;
}
@@ -4665,8 +4673,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,
@@ -4682,20 +4690,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
@@ -4750,8 +4759,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) &&
@@ -4775,14 +4784,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)
@@ -4806,12 +4816,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);
}
@@ -4822,10 +4832,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:
//
@@ -4834,9 +4845,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;
@@ -4897,8 +4908,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);
@@ -4910,8 +4921,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() &&
@@ -4921,17 +4932,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;
@@ -4957,8 +4970,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;
@@ -4987,7 +5000,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 f4b205d9d55624cc5732031891117dcef4cabd25 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/30] 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 ffdf90ae646a2..ebe4a31def815 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6044,6 +6044,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 cd84c6a4c312b..c5db51c76509d 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.
@@ -905,7 +906,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 f3ae6e39ca98e..29f540a261785 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8703,19 +8703,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.
@@ -15418,129 +15405,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 006bc7004de7695273d16983dce6f03fd4501a19 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/30] 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 2bac5e7b06a59..0ff9e600b5449 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -410,6 +410,7 @@ clang-format
constructor initializers after commas, keeping the colon on the same line.
- Extend ``BreakBinaryOperations`` to accept a structured configuration with
per-operator break rules and minimum chain length gating via ``PerOperator``.
+- 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 c5db51c76509d..8f7d540b032e3 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,
@@ -962,12 +964,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 5ca4d304859bb66a6ba376942cf363584f1f2bf6 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/30] 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 c662ae5c44e4e..71218eb9ec8ae 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5801,6 +5801,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 8f7d540b032e3..e727dbf3c80b7 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,
@@ -907,7 +907,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;
}
@@ -964,14 +968,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 8ce9f52632206fbc3e663d9fd00480786cb74c0a 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/30] 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 e727dbf3c80b7..717bacf5bd07f 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;
}
@@ -912,6 +914,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 29f540a261785..53e706d3433d1 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15431,6 +15431,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 c601c10a66043a77603975f63aff2fd3e191f0fc 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/30] 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 717bacf5bd07f..f6932ca1b6726 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 53e706d3433d1..ee668ca9d0fb1 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15431,9 +15431,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;
@@ -15469,6 +15466,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 452016b2d8abd10c081fde26506763d35fbd818f 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/30] 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 f6932ca1b6726..d3e4432496542 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 63a663b71b14f..26967d8975120 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 8e26587bd9590715ffa050ebcabe0cb627c9c193 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/30] 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 d3e4432496542..3da44e32c409b 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 16d0bc90c0ca06945a67160e8ccacc8e417bf732 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/30] 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 3da44e32c409b..81a893d7054fd 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 ac5adb926c3b9b7ecf320259e52a98bffd7f2da4 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/30] 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 81a893d7054fd..b1ebf04b51fcb 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 57752585596f531c1dac85c817a30dd992ae12d4 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/30] 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 f273ba2738b6c..b6d4383447541 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1745,9 +1745,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 ebe4a31def815..847c9106907f7 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6046,13 +6046,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 b1ebf04b51fcb..6abfebbd21406 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 cae57d5b09129ad7167316e4e3e91db324ce5ff9 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/30] 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 6abfebbd21406..de3291daf8d00 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 ee668ca9d0fb1..e5e2c60a30224 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15481,7 +15481,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 fbf21335f530c6e24c8088746fb582a00cee29cd 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/30] 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 847c9106907f7..ca1cd692df9b7 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6047,11 +6047,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 ff7da89f64af9456fe80541c34e0e51351e6461b 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/30] 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 de3291daf8d00..bc4be0c94f412 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 eceb8df87a28f0610617f8c1b847ae630b409cfc 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/30] 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 bc4be0c94f412..f0769ddaa83fd 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)) {
>From 85ceec69a0d9865432ad87cc81e714a19264b1e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Thu, 4 Dec 2025 21:33:35 +0100
Subject: [PATCH 26/30] Minor fixups
---
clang/lib/Format/TokenAnnotator.cpp | 1 -
clang/lib/Format/UnwrappedLineFormatter.cpp | 5 +++--
clang/lib/Format/UnwrappedLineParser.cpp | 3 +--
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index ca1cd692df9b7..7273c55f6e8bc 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6044,7 +6044,6 @@ 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 Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never &&
((Line.startsWith(tok::kw_class) &&
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f0769ddaa83fd..273fdb57304ae 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -606,7 +606,7 @@ class LineJoiner {
return Style.BraceWrapping.AfterUnion;
default:
return false;
- };
+ }
};
auto TryMergeShortRecord = [&] {
@@ -615,7 +615,8 @@ class LineJoiner {
return false;
case FormatStyle::SRS_Always:
return true;
- default:
+ case FormatStyle::SRS_EmptyIfAttached:
+ 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 26967d8975120..0440413ff373b 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -3265,8 +3265,7 @@ 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;
>From 0bc47fc73acba87fb956b49ee965bbbee9ca8db6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 28 Jan 2026 21:42:17 +0100
Subject: [PATCH 27/30] Bump version and revert oddly reformatted comment
blocks
---
clang/include/clang/Format/Format.h | 2 +-
clang/lib/Format/UnwrappedLineParser.cpp | 213 +++++++++++------------
2 files changed, 102 insertions(+), 113 deletions(-)
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 71218eb9ec8ae..83e8705a1a0c9 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1015,7 +1015,7 @@ struct FormatStyle {
/// Dependent on the value, ``struct bar { int i; };`` can be put on a single
/// line.
- /// \version 22
+ /// \version 23
ShortRecordStyle AllowShortRecordOnASingleLine;
/// Different ways to break after the function definition return type.
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 0440413ff373b..193e0dec7a2ad 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -3319,8 +3319,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();
@@ -3478,8 +3478,7 @@ 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.
@@ -3542,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();
@@ -3615,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);
@@ -3661,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;
@@ -3808,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;
@@ -4121,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 {
@@ -4173,13 +4172,12 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
}
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.
}
@@ -4267,8 +4265,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();
@@ -4306,8 +4304,7 @@ 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;
}
@@ -4342,9 +4339,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)) {
@@ -4352,10 +4349,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) &&
@@ -4367,8 +4364,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)) {
@@ -4547,11 +4544,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))
@@ -4559,8 +4556,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;
@@ -4605,24 +4602,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()) {
@@ -4640,9 +4637,8 @@ 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));
}
@@ -4660,8 +4656,7 @@ 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;
}
@@ -4672,8 +4667,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,
@@ -4689,21 +4684,20 @@ 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
@@ -4758,8 +4752,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) &&
@@ -4783,15 +4777,14 @@ 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)
@@ -4815,12 +4808,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);
}
@@ -4831,11 +4824,10 @@ 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:
//
@@ -4844,9 +4836,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;
@@ -4907,8 +4899,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);
@@ -4920,8 +4912,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() &&
@@ -4931,19 +4923,17 @@ 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;
@@ -4969,8 +4959,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;
@@ -4999,8 +4989,7 @@ 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 7736a21ac9d06e978791ce3aef0ef3a2322a8bd2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 23 Feb 2026 03:16:12 +0100
Subject: [PATCH 28/30] Regenerate ClangFormatStyleOptions.rst
---
clang/docs/ClangFormatStyleOptions.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index a11ffe8d30c5d..1d5bb18dc9494 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2087,7 +2087,7 @@ the configuration (without a prefix: ``Auto``).
.. _AllowShortRecordOnASingleLine:
-**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>`
+**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 23` :ref:`¶ <AllowShortRecordOnASingleLine>`
Dependent on the value, ``struct bar { int i; };`` can be put on a single
line.
>From 68062957f5db334f21f8a5c7f09a6a0d4bca2a4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 9 Mar 2026 18:58:10 +0100
Subject: [PATCH 29/30] Change EmptyIfAttached to EmptyAndAttached, address
missed comments
---
clang/docs/ClangFormatStyleOptions.rst | 2 +-
clang/docs/ReleaseNotes.rst | 2 +-
clang/include/clang/Format/Format.h | 2 +-
clang/lib/Format/Format.cpp | 4 ++--
clang/lib/Format/UnwrappedLineFormatter.cpp | 6 ++----
clang/unittests/Format/ConfigParseTest.cpp | 6 +++---
clang/unittests/Format/FormatTest.cpp | 2 +-
7 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 1d5bb18dc9494..964f12440ef52 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2096,7 +2096,7 @@ the configuration (without a prefix: ``Auto``).
* ``SRS_Never`` (in configuration: ``Never``)
Never merge records into a single line.
- * ``SRS_EmptyIfAttached`` (in configuration: ``EmptyIfAttached``)
+ * ``SRS_EmptyAndAttached`` (in configuration: ``EmptyAndAttached``)
Only merge empty records if the opening brace was not wrapped,
i.e. the corresponding ``BraceWrapping.After...`` option was not set.
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0ff9e600b5449..3712cda3ad072 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -410,7 +410,7 @@ clang-format
constructor initializers after commas, keeping the colon on the same line.
- Extend ``BreakBinaryOperations`` to accept a structured configuration with
per-operator break rules and minimum chain length gating via ``PerOperator``.
-- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyIfAttached`` for LLVM style.
+- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyAndAttached`` for LLVM style.
libclang
--------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 83e8705a1a0c9..bd79d643397ec 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -995,7 +995,7 @@ struct FormatStyle {
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,
+ SRS_EmptyAndAttached,
/// Only merge empty records.
/// \code
/// struct foo {};
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index b6d4383447541..83267a8875fc8 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -770,7 +770,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, "EmptyAndAttached", FormatStyle::SRS_EmptyAndAttached);
IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty);
IO.enumCase(Value, "Always", FormatStyle::SRS_Always);
}
@@ -1747,7 +1747,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
- LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyAndAttached;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
LLVMStyle.AlwaysBreakBeforeMultilineStrings = false;
LLVMStyle.AttributeMacros.push_back("__capability");
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 273fdb57304ae..12895c65be78e 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>
@@ -521,7 +520,7 @@ class LineJoiner {
!Style.BraceWrapping.SplitEmptyRecord);
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
- tok::kw_struct, Keywords.kw_record)) {
+ tok::kw_struct, tok::kw_union)) {
// Try to merge a block with left brace unwrapped that wasn't yet
// covered.
ShouldMerge = !Style.BraceWrapping.AfterFunction ||
@@ -615,8 +614,7 @@ class LineJoiner {
return false;
case FormatStyle::SRS_Always:
return true;
- case FormatStyle::SRS_EmptyIfAttached:
- case FormatStyle::SRS_Empty:
+ default:
return NextLine->First->is(tok::r_brace);
}
};
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 4f32bbfe9d130..b03eb6cf6b094 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -716,11 +716,11 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("AllowShortLambdasOnASingleLine: true",
AllowShortLambdasOnASingleLine, FormatStyle::SLS_All);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyAndAttached;
CHECK_PARSE("AllowShortRecordOnASingleLine: Never",
AllowShortRecordOnASingleLine, FormatStyle::SRS_Never);
- CHECK_PARSE("AllowShortRecordOnASingleLine: EmptyIfAttached",
- AllowShortRecordOnASingleLine, FormatStyle::SRS_EmptyIfAttached);
+ CHECK_PARSE("AllowShortRecordOnASingleLine: EmptyAndAttached",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_EmptyAndAttached);
CHECK_PARSE("AllowShortRecordOnASingleLine: Empty",
AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty);
CHECK_PARSE("AllowShortRecordOnASingleLine: Always",
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index e5e2c60a30224..56961977ddb0b 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15408,7 +15408,7 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
auto Style = getLLVMStyle();
EXPECT_EQ(Style.AllowShortRecordOnASingleLine,
- FormatStyle::SRS_EmptyIfAttached);
+ FormatStyle::SRS_EmptyAndAttached);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo {\n"
>From 9af8486c0a24095f01b69bb88d99e07ab09a80d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sat, 14 Mar 2026 04:20:32 +0100
Subject: [PATCH 30/30] Missing kw_union, fix keyword order
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 12895c65be78e..2dc163032c8d1 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -487,7 +487,8 @@ class LineJoiner {
const FormatToken *PreviousPrevious =
Previous->getPreviousNonComment();
if (PreviousPrevious &&
- PreviousPrevious->isOneOf(tok::kw_class, tok::kw_struct)) {
+ PreviousPrevious->isOneOf(tok::kw_class, tok::kw_struct,
+ tok::kw_union)) {
return 0;
}
}
@@ -640,7 +641,7 @@ class LineJoiner {
// Previous line begins record, current line block.
if (I != AnnotatedLines.begin() &&
- I[-1]->First->isOneOf(tok::kw_struct, tok::kw_class, tok::kw_union)) {
+ I[-1]->First->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union)) {
const bool IsEmptyBlock =
Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
More information about the cfe-commits
mailing list