[clang] [clang-format] Add option AllowShortRecordOnASingleLine (PR #154580)
Tomáš Slanina via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 27 17:35:27 PST 2025
https://github.com/itzexpoexpo updated https://github.com/llvm/llvm-project/pull/154580
>From 46f08a12830afac8db86cb0c476e57282461a39e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 20 Aug 2025 19:28:23 +0200
Subject: [PATCH 01/25] [clang-format] Add option
AllowShortRecordsOnASingleLine
This commit supersedes PR #151970 by adding the option
AllowShortRecordsOnASingleLine that allows the following formatting:
struct foo {};
struct bar { int i; };
struct baz
{
int i;
int j;
int k;
};
---
clang/docs/ClangFormatStyleOptions.rst | 32 +++++++++
clang/include/clang/Format/Format.h | 26 ++++++++
clang/lib/Format/Format.cpp | 11 ++++
clang/lib/Format/TokenAnnotator.cpp | 13 ++--
clang/lib/Format/UnwrappedLineFormatter.cpp | 22 ++++++-
clang/lib/Format/UnwrappedLineParser.cpp | 24 +++++--
clang/unittests/Format/ConfigParseTest.cpp | 8 +++
clang/unittests/Format/FormatTest.cpp | 73 +++++++++++++++++++++
8 files changed, 195 insertions(+), 14 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 4f81a084dd65b..50483709c4909 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2085,6 +2085,38 @@ the configuration (without a prefix: ``Auto``).
**AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>`
If ``true``, ``namespace a { class b; }`` can be put on a single line.
+.. _AllowShortRecordsOnASingleLine:
+
+**AllowShortRecordsOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordsOnASingleLine>`
+ Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ line.
+
+ Possible values:
+
+ * ``SRS_Never`` (in configuration: ``Never``)
+ Never merge records into a single line.
+
+ * ``SRS_Empty`` (in configuration: ``Empty``)
+ Only merge empty records.
+
+ .. code-block:: c++
+
+ struct foo {};
+ struct bar
+ {
+ int i;
+ };
+
+ * ``SRS_All`` (in configuration: ``All``)
+ Merge all records that fit on a single line.
+
+ .. code-block:: c++
+
+ struct foo {};
+ struct bar { int i; };
+
+
+
.. _AlwaysBreakAfterDefinitionReturnType:
**AlwaysBreakAfterDefinitionReturnType** (``DefinitionReturnTypeBreakingStyle``) :versionbadge:`clang-format 3.7` :ref:`¶ <AlwaysBreakAfterDefinitionReturnType>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index c7e57d47f9ed1..e99d0d0871bcb 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -987,6 +987,32 @@ struct FormatStyle {
/// \version 20
bool AllowShortNamespacesOnASingleLine;
+ /// Different styles for merging short records
+ /// (``class``,``struct``,``union``).
+ enum ShortRecordStyle : int8_t {
+ /// Never merge records into a single line.
+ SRS_Never,
+ /// Only merge empty records.
+ /// \code
+ /// struct foo {};
+ /// struct bar
+ /// {
+ /// int i;
+ /// };
+ /// \endcode
+ SRS_Empty,
+ /// Merge all records that fit on a single line.
+ /// \code
+ /// struct foo {};
+ /// struct bar { int i; };
+ /// \endcode
+ SRS_All
+ };
+
+ /// Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ /// line.
+ ShortRecordStyle AllowShortRecordsOnASingleLine;
+
/// Different ways to break after the function definition return type.
/// This option is **deprecated** and is retained for backwards compatibility.
enum DefinitionReturnTypeBreakingStyle : int8_t {
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index f0e9aff2fd21a..e973122b132c9 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -708,6 +708,14 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> {
}
};
+template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> {
+ static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) {
+ IO.enumCase(Value, "Never", FormatStyle::SRS_Never);
+ IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty);
+ IO.enumCase(Value, "All", FormatStyle::SRS_All);
+ }
+};
+
template <> struct MappingTraits<FormatStyle::SortIncludesOptions> {
static void enumInput(IO &IO, FormatStyle::SortIncludesOptions &Value) {
IO.enumCase(Value, "Never", FormatStyle::SortIncludesOptions({}));
@@ -1121,6 +1129,8 @@ template <> struct MappingTraits<FormatStyle> {
Style.AllowShortIfStatementsOnASingleLine);
IO.mapOptional("AllowShortLambdasOnASingleLine",
Style.AllowShortLambdasOnASingleLine);
+ IO.mapOptional("AllowShortRecordsOnASingleLine",
+ Style.AllowShortRecordsOnASingleLine);
IO.mapOptional("AllowShortLoopsOnASingleLine",
Style.AllowShortLoopsOnASingleLine);
IO.mapOptional("AllowShortNamespacesOnASingleLine",
@@ -1673,6 +1683,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
+ LLVMStyle.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 79cfa73001e54..fda57d0508767 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5977,12 +5977,15 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
return true;
}
- // Don't attempt to interpret struct return types as structs.
+ // Don't attempt to interpret record return types as records.
if (Right.isNot(TT_FunctionLBrace)) {
- return (Line.startsWith(tok::kw_class) &&
- Style.BraceWrapping.AfterClass) ||
- (Line.startsWith(tok::kw_struct) &&
- Style.BraceWrapping.AfterStruct);
+ return ((Line.startsWith(tok::kw_class) &&
+ Style.BraceWrapping.AfterClass) ||
+ (Line.startsWith(tok::kw_struct) &&
+ Style.BraceWrapping.AfterStruct) ||
+ (Line.startsWith(tok::kw_union) &&
+ Style.BraceWrapping.AfterUnion)) &&
+ Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never;
}
}
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index d31d656a63fc5..caf6e5c9fd930 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,6 +463,19 @@ class LineJoiner {
}
}
+ auto ShouldMergeShortRecords = [this, &I, &NextLine, PreviousLine,
+ TheLine]() {
+ if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_All)
+ return true;
+ if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Empty &&
+ NextLine.First->is(tok::r_brace)) {
+ return true;
+ }
+ return false;
+ };
+
+ bool MergeShortRecord = ShouldMergeShortRecords();
+
// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord &&
@@ -506,7 +519,8 @@ class LineJoiner {
// elsewhere.
ShouldMerge = !Style.BraceWrapping.AfterClass ||
(NextLine.First->is(tok::r_brace) &&
- !Style.BraceWrapping.SplitEmptyRecord);
+ !Style.BraceWrapping.SplitEmptyRecord) ||
+ MergeShortRecord;
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct, Keywords.kw_record)) {
@@ -881,9 +895,11 @@ class LineJoiner {
return 1;
} else if (Limit != 0 && !Line.startsWithNamespace() &&
!startsExternCBlock(Line)) {
- // We don't merge short records.
- if (isRecordLBrace(*Line.Last))
+ // Merge short records only when requested.
+ if (isRecordLBrace(*Line.Last) &&
+ Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never) {
return 0;
+ }
// Check that we still have three lines and they fit into the limit.
if (I + 2 == E || I[2]->Type == LT_Invalid)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 19c83d3910902..2b6728ff296b9 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -949,6 +949,7 @@ static bool isIIFE(const UnwrappedLine &Line,
static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
const FormatToken &InitialToken,
+ const FormatToken &NextToken,
const bool IsJavaRecord) {
if (IsJavaRecord)
return Style.BraceWrapping.AfterClass;
@@ -957,15 +958,20 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
if (InitialToken.is(TT_NamespaceMacro))
Kind = tok::kw_namespace;
+ bool IsEmptyBlock = NextToken.is(tok::r_brace);
+ bool WrapRecordAllowed =
+ !(IsEmptyBlock &&
+ Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never);
+
switch (Kind) {
case tok::kw_namespace:
return Style.BraceWrapping.AfterNamespace;
case tok::kw_class:
- return Style.BraceWrapping.AfterClass;
+ return Style.BraceWrapping.AfterClass && WrapRecordAllowed;
case tok::kw_union:
- return Style.BraceWrapping.AfterUnion;
+ return Style.BraceWrapping.AfterUnion && WrapRecordAllowed;
case tok::kw_struct:
- return Style.BraceWrapping.AfterStruct;
+ return Style.BraceWrapping.AfterStruct && WrapRecordAllowed;
case tok::kw_enum:
return Style.BraceWrapping.AfterEnum;
default:
@@ -3202,8 +3208,10 @@ void UnwrappedLineParser::parseNamespace() {
if (FormatTok->is(tok::l_brace)) {
FormatTok->setFinalizedType(TT_NamespaceLBrace);
- if (ShouldBreakBeforeBrace(Style, InitialToken, /*IsJavaRecord=*/false))
+ if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ /*IsJavaRecord=*/false)) {
addUnwrappedLine();
+ }
unsigned AddLevels =
Style.NamespaceIndentation == FormatStyle::NI_All ||
@@ -3857,7 +3865,8 @@ bool UnwrappedLineParser::parseEnum() {
}
if (!Style.AllowShortEnumsOnASingleLine &&
- ShouldBreakBeforeBrace(Style, InitialToken, /*IsJavaRecord=*/false)) {
+ ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ /*IsJavaRecord=*/false)) {
addUnwrappedLine();
}
// Parse enum body.
@@ -4152,8 +4161,11 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (ShouldBreakBeforeBrace(Style, InitialToken, IsJavaRecord))
+ if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+ ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ IsJavaRecord)) {
addUnwrappedLine();
+ }
unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index fec1c48c448d2..729e96f89a03c 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,6 +668,14 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("AllowShortLambdasOnASingleLine: true",
AllowShortLambdasOnASingleLine, FormatStyle::SLS_All);
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty);
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: All",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_All);
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: Never",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never);
+
Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both;
CHECK_PARSE("SpaceAroundPointerQualifiers: Default",
SpaceAroundPointerQualifiers, FormatStyle::SAPQ_Default);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 5a5d77075bb3a..89abe5dae7075 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8655,6 +8655,19 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) {
Style);
}
+TEST_F(FormatTest, BreakFunctionsReturningRecords) {
+ FormatStyle Style = getLLVMStyle();
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterFunction = true;
+ Style.BraceWrapping.AfterClass = false;
+ Style.BraceWrapping.AfterStruct = false;
+ Style.BraceWrapping.AfterUnion = false;
+
+ verifyFormat("class Bar foo() {}", Style);
+ verifyFormat("struct Bar foo() {}", Style);
+ verifyFormat("union Bar foo() {}", Style);
+}
+
TEST_F(FormatTest, DontBreakBeforeQualifiedOperator) {
// Regression test for https://bugs.llvm.org/show_bug.cgi?id=40516:
// Prefer keeping `::` followed by `operator` together.
@@ -15357,6 +15370,66 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
Style);
}
+TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
+ FormatStyle Style = getLLVMStyle();
+
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterClass = true;
+ Style.BraceWrapping.AfterStruct = true;
+ Style.BraceWrapping.AfterUnion = true;
+ Style.BraceWrapping.SplitEmptyRecord = false;
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+
+ verifyFormat("class foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo\n{};", Style);
+
+ verifyFormat("struct foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo\n{};", Style);
+
+ verifyFormat("union foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo\n{};", Style);
+
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Empty;
+
+ verifyFormat("class foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo {};", Style);
+
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_All;
+
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo { int bar; };", Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo { int bar; };", Style);
+ verifyFormat("union foo {};", Style);
+}
+
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
// Elaborate type variable declarations.
verifyFormat("struct foo a = {bar};\nint n;");
>From 3e4fdc103802204f62d81ae7520b5dfbe9f29587 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 20 Aug 2025 23:54:07 +0200
Subject: [PATCH 02/25] Fixup: option order, inline MergeShortRecord lambda
---
clang/lib/Format/Format.cpp | 4 ++--
clang/lib/Format/UnwrappedLineFormatter.cpp | 18 ++++++++----------
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index e973122b132c9..187bec0041910 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1129,12 +1129,12 @@ template <> struct MappingTraits<FormatStyle> {
Style.AllowShortIfStatementsOnASingleLine);
IO.mapOptional("AllowShortLambdasOnASingleLine",
Style.AllowShortLambdasOnASingleLine);
- IO.mapOptional("AllowShortRecordsOnASingleLine",
- Style.AllowShortRecordsOnASingleLine);
IO.mapOptional("AllowShortLoopsOnASingleLine",
Style.AllowShortLoopsOnASingleLine);
IO.mapOptional("AllowShortNamespacesOnASingleLine",
Style.AllowShortNamespacesOnASingleLine);
+ IO.mapOptional("AllowShortRecordsOnASingleLine",
+ Style.AllowShortRecordsOnASingleLine);
IO.mapOptional("AlwaysBreakAfterDefinitionReturnType",
Style.AlwaysBreakAfterDefinitionReturnType);
IO.mapOptional("AlwaysBreakBeforeMultilineStrings",
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index caf6e5c9fd930..73d903558b2ec 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,18 +463,16 @@ class LineJoiner {
}
}
- auto ShouldMergeShortRecords = [this, &I, &NextLine, PreviousLine,
- TheLine]() {
- if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_All)
- return true;
- if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Empty &&
- NextLine.First->is(tok::r_brace)) {
+ const bool MergeShortRecord = [this, &NextLine]() {
+ switch (Style.AllowShortRecordsOnASingleLine) {
+ case FormatStyle::SRS_All:
return true;
+ case FormatStyle::SRS_Empty:
+ return NextLine.First->is(tok::r_brace);
+ case FormatStyle::SRS_Never:
+ return false;
}
- return false;
- };
-
- bool MergeShortRecord = ShouldMergeShortRecords();
+ }();
// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
>From f161f3c76d1ecaf96e504aa1a49dc2a72a6344e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:24:59 +0200
Subject: [PATCH 03/25] Use consistently named enum values
---
clang/include/clang/Format/Format.h | 2 +-
clang/lib/Format/Format.cpp | 2 +-
clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +-
clang/lib/Format/UnwrappedLineParser.cpp | 2 +-
clang/unittests/Format/ConfigParseTest.cpp | 4 ++--
clang/unittests/Format/FormatTest.cpp | 2 +-
6 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index e99d0d0871bcb..7127cd4eb36e4 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1006,7 +1006,7 @@ struct FormatStyle {
/// struct foo {};
/// struct bar { int i; };
/// \endcode
- SRS_All
+ SRS_Always
};
/// Dependent on the value, ``struct bar { int i; }`` can be put on a single
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 187bec0041910..8952c6bcfdbbd 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -712,7 +712,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> {
static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) {
IO.enumCase(Value, "Never", FormatStyle::SRS_Never);
IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty);
- IO.enumCase(Value, "All", FormatStyle::SRS_All);
+ IO.enumCase(Value, "Always", FormatStyle::SRS_Always);
}
};
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 73d903558b2ec..52b905a31377b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -465,7 +465,7 @@ class LineJoiner {
const bool MergeShortRecord = [this, &NextLine]() {
switch (Style.AllowShortRecordsOnASingleLine) {
- case FormatStyle::SRS_All:
+ case FormatStyle::SRS_Always:
return true;
case FormatStyle::SRS_Empty:
return NextLine.First->is(tok::r_brace);
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 2b6728ff296b9..4367ed80635f2 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4161,7 +4161,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+ if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always &&
ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
IsJavaRecord)) {
addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 729e96f89a03c..bf74434cf4b55 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -671,8 +671,8 @@ TEST(ConfigParseTest, ParsesConfiguration) {
Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty",
AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty);
- CHECK_PARSE("AllowShortRecordsOnASingleLine: All",
- AllowShortRecordsOnASingleLine, FormatStyle::SRS_All);
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: Always",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_Always);
CHECK_PARSE("AllowShortRecordsOnASingleLine: Never",
AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 89abe5dae7075..2481ccbd3423e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15418,7 +15418,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo {};", Style);
- Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_All;
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Always;
verifyFormat("class foo { void bar(); };", Style);
verifyFormat("class foo {};", Style);
>From fe76a5fb48711b06ec8d899e3d87f6ae1133fa4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:54:21 +0200
Subject: [PATCH 04/25] Change option name to use singular form
---
clang/include/clang/Format/Format.h | 2 +-
clang/lib/Format/Format.cpp | 6 +++---
clang/lib/Format/TokenAnnotator.cpp | 2 +-
clang/lib/Format/UnwrappedLineFormatter.cpp | 4 ++--
clang/lib/Format/UnwrappedLineParser.cpp | 4 ++--
clang/unittests/Format/ConfigParseTest.cpp | 14 +++++++-------
clang/unittests/Format/FormatTest.cpp | 6 +++---
7 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 7127cd4eb36e4..800fe5c922d04 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1011,7 +1011,7 @@ struct FormatStyle {
/// Dependent on the value, ``struct bar { int i; }`` can be put on a single
/// line.
- ShortRecordStyle AllowShortRecordsOnASingleLine;
+ ShortRecordStyle AllowShortRecordOnASingleLine;
/// Different ways to break after the function definition return type.
/// This option is **deprecated** and is retained for backwards compatibility.
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 8952c6bcfdbbd..c17348fe7e07a 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1133,8 +1133,8 @@ template <> struct MappingTraits<FormatStyle> {
Style.AllowShortLoopsOnASingleLine);
IO.mapOptional("AllowShortNamespacesOnASingleLine",
Style.AllowShortNamespacesOnASingleLine);
- IO.mapOptional("AllowShortRecordsOnASingleLine",
- Style.AllowShortRecordsOnASingleLine);
+ IO.mapOptional("AllowShortRecordOnASingleLine",
+ Style.AllowShortRecordOnASingleLine);
IO.mapOptional("AlwaysBreakAfterDefinitionReturnType",
Style.AlwaysBreakAfterDefinitionReturnType);
IO.mapOptional("AlwaysBreakBeforeMultilineStrings",
@@ -1683,7 +1683,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
- LLVMStyle.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+ LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index fda57d0508767..efd2a35a79bcd 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5985,7 +5985,7 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
Style.BraceWrapping.AfterStruct) ||
(Line.startsWith(tok::kw_union) &&
Style.BraceWrapping.AfterUnion)) &&
- Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never;
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never;
}
}
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 52b905a31377b..c1b433c06bdaf 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -464,7 +464,7 @@ class LineJoiner {
}
const bool MergeShortRecord = [this, &NextLine]() {
- switch (Style.AllowShortRecordsOnASingleLine) {
+ switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Always:
return true;
case FormatStyle::SRS_Empty:
@@ -895,7 +895,7 @@ class LineJoiner {
!startsExternCBlock(Line)) {
// Merge short records only when requested.
if (isRecordLBrace(*Line.Last) &&
- Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never) {
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never) {
return 0;
}
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 4367ed80635f2..4f483eb533240 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -961,7 +961,7 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
bool IsEmptyBlock = NextToken.is(tok::r_brace);
bool WrapRecordAllowed =
!(IsEmptyBlock &&
- Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never);
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never);
switch (Kind) {
case tok::kw_namespace:
@@ -4161,7 +4161,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Always &&
+ if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always &&
ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
IsJavaRecord)) {
addUnwrappedLine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index bf74434cf4b55..3afaba3359e3b 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,13 +668,13 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("AllowShortLambdasOnASingleLine: true",
AllowShortLambdasOnASingleLine, FormatStyle::SLS_All);
- Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
- CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty",
- AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty);
- CHECK_PARSE("AllowShortRecordsOnASingleLine: Always",
- AllowShortRecordsOnASingleLine, FormatStyle::SRS_Always);
- CHECK_PARSE("AllowShortRecordsOnASingleLine: Never",
- AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ CHECK_PARSE("AllowShortRecordOnASingleLine: Empty",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty);
+ CHECK_PARSE("AllowShortRecordOnASingleLine: Always",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_Always);
+ CHECK_PARSE("AllowShortRecordOnASingleLine: Never",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_Never);
Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both;
CHECK_PARSE("SpaceAroundPointerQualifiers: Default",
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 2481ccbd3423e..cca4d00152fde 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15378,7 +15378,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style.BraceWrapping.AfterStruct = true;
Style.BraceWrapping.AfterUnion = true;
Style.BraceWrapping.SplitEmptyRecord = false;
- Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo\n{\n"
" void bar();\n"
@@ -15398,7 +15398,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo\n{};", Style);
- Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Empty;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
verifyFormat("class foo\n{\n"
" void bar();\n"
@@ -15418,7 +15418,7 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo {};", Style);
- Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Always;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
verifyFormat("class foo { void bar(); };", Style);
verifyFormat("class foo {};", Style);
>From baa82ac6d0e1f7144a73bc01339b153e44ca4184 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 24 Aug 2025 15:56:58 +0200
Subject: [PATCH 05/25] Fix docs after rename
---
clang/docs/ClangFormatStyleOptions.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 50483709c4909..485f2ee9f4451 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2085,9 +2085,9 @@ the configuration (without a prefix: ``Auto``).
**AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>`
If ``true``, ``namespace a { class b; }`` can be put on a single line.
-.. _AllowShortRecordsOnASingleLine:
+.. _AllowShortRecordOnASingleLine:
-**AllowShortRecordsOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordsOnASingleLine>`
+**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordOnASingleLine>`
Dependent on the value, ``struct bar { int i; }`` can be put on a single
line.
@@ -2107,7 +2107,7 @@ the configuration (without a prefix: ``Auto``).
int i;
};
- * ``SRS_All`` (in configuration: ``All``)
+ * ``SRS_Always`` (in configuration: ``Always``)
Merge all records that fit on a single line.
.. code-block:: c++
>From 20ca8e7665f72bc738de23664fdadfd51e6cc7ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 25 Aug 2025 00:38:53 +0200
Subject: [PATCH 06/25] Fixup documentation
---
clang/docs/ClangFormatStyleOptions.rst | 2 +-
clang/include/clang/Format/Format.h | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 485f2ee9f4451..3896ce095bb3e 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2087,7 +2087,7 @@ the configuration (without a prefix: ``Auto``).
.. _AllowShortRecordOnASingleLine:
-**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordOnASingleLine>`
+**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>`
Dependent on the value, ``struct bar { int i; }`` can be put on a single
line.
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 800fe5c922d04..a3790bd9e481e 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -987,8 +987,8 @@ struct FormatStyle {
/// \version 20
bool AllowShortNamespacesOnASingleLine;
- /// Different styles for merging short records
- /// (``class``,``struct``,``union``).
+ /// Different styles for merging short records (``class``,``struct``, and
+ /// ``union``).
enum ShortRecordStyle : int8_t {
/// Never merge records into a single line.
SRS_Never,
@@ -1011,6 +1011,7 @@ struct FormatStyle {
/// Dependent on the value, ``struct bar { int i; }`` can be put on a single
/// line.
+ /// \version 22
ShortRecordStyle AllowShortRecordOnASingleLine;
/// Different ways to break after the function definition return type.
>From 3a3314290a38c0a398a3fda42d5f539595d10e7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Tue, 26 Aug 2025 23:54:53 +0200
Subject: [PATCH 07/25] Fix SplitEmptyRecord handling, docs
---
clang/docs/ClangFormatStyleOptions.rst | 2 +-
clang/include/clang/Format/Format.h | 2 +-
clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +-
clang/lib/Format/UnwrappedLineParser.cpp | 3 ++-
clang/unittests/Format/FormatTest.cpp | 25 ++++++++++++---------
5 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 3896ce095bb3e..eb655a4b0b54a 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2088,7 +2088,7 @@ the configuration (without a prefix: ``Auto``).
.. _AllowShortRecordOnASingleLine:
**AllowShortRecordOnASingleLine** (``ShortRecordStyle``) :versionbadge:`clang-format 22` :ref:`¶ <AllowShortRecordOnASingleLine>`
- Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ Dependent on the value, ``struct bar { int i; };`` can be put on a single
line.
Possible values:
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index a3790bd9e481e..64430d4158abe 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1009,7 +1009,7 @@ struct FormatStyle {
SRS_Always
};
- /// Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ /// Dependent on the value, ``struct bar { int i; };`` can be put on a single
/// line.
/// \version 22
ShortRecordStyle AllowShortRecordOnASingleLine;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index c1b433c06bdaf..a34724dc751cd 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -472,7 +472,7 @@ class LineJoiner {
case FormatStyle::SRS_Never:
return false;
}
- }();
+ }() && !Style.BraceWrapping.SplitEmptyRecord;
// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 4f483eb533240..f355d5cb0c6af 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -961,7 +961,8 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
bool IsEmptyBlock = NextToken.is(tok::r_brace);
bool WrapRecordAllowed =
!(IsEmptyBlock &&
- Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never);
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) ||
+ Style.BraceWrapping.SplitEmptyRecord;
switch (Kind) {
case tok::kw_namespace:
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index cca4d00152fde..f7fbc095c927f 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15398,6 +15398,17 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo\n{};", Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo { int bar; };", Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo { int bar; };", Style);
+ verifyFormat("union foo {};", Style);
+
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
verifyFormat("class foo\n{\n"
@@ -15418,16 +15429,10 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo {};", Style);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
-
- verifyFormat("class foo { void bar(); };", Style);
- verifyFormat("class foo {};", Style);
-
- verifyFormat("struct foo { int bar; };", Style);
- verifyFormat("struct foo {};", Style);
-
- verifyFormat("union foo { int bar; };", Style);
- verifyFormat("union foo {};", Style);
+ Style.BraceWrapping.SplitEmptyRecord = true;
+ verifyFormat("class foo\n{\n}", Style);
+ verifyFormat("struct foo\n{\n}", Style);
+ verifyFormat("union foo\n{\n}", Style);
}
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
>From fe2922e26caaeef5a0089d68db58f7b1ff1a0bc8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sat, 30 Aug 2025 17:41:51 +0200
Subject: [PATCH 08/25] Fix behavior of Never, add EmptyIfAttached
---
clang/docs/ClangFormatStyleOptions.rst | 4 +
clang/include/clang/Format/Format.h | 3 +
clang/lib/Format/Format.cpp | 3 +-
clang/lib/Format/UnwrappedLineFormatter.cpp | 42 ++++----
clang/lib/Format/UnwrappedLineParser.cpp | 4 +-
clang/unittests/Format/ConfigParseTest.cpp | 8 +-
clang/unittests/Format/FormatTest.cpp | 114 ++++++++++++++++++--
7 files changed, 143 insertions(+), 35 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index eb655a4b0b54a..ff6ef2c26176d 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2096,6 +2096,10 @@ the configuration (without a prefix: ``Auto``).
* ``SRS_Never`` (in configuration: ``Never``)
Never merge records into a single line.
+ * ``SRS_EmptyIfAttached`` (in configuration: ``EmptyIfAttached``)
+ Only merge empty records if the opening brace was not wrapped,
+ i.e. the corresponding ``BraceWrapping.After...`` option was not set.
+
* ``SRS_Empty`` (in configuration: ``Empty``)
Only merge empty records.
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 64430d4158abe..3ad8301342fb6 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -992,6 +992,9 @@ struct FormatStyle {
enum ShortRecordStyle : int8_t {
/// Never merge records into a single line.
SRS_Never,
+ /// Only merge empty records if the opening brace was not wrapped,
+ /// i.e. the corresponding ``BraceWrapping.After...`` option was not set.
+ SRS_EmptyIfAttached,
/// Only merge empty records.
/// \code
/// struct foo {};
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index c17348fe7e07a..750c642ae0db4 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -711,6 +711,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> {
template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> {
static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) {
IO.enumCase(Value, "Never", FormatStyle::SRS_Never);
+ IO.enumCase(Value, "EmptyIfAttached", FormatStyle::SRS_EmptyIfAttached);
IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty);
IO.enumCase(Value, "Always", FormatStyle::SRS_Always);
}
@@ -1683,7 +1684,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
- LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index a34724dc751cd..4d7c703faab57 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -463,17 +463,6 @@ class LineJoiner {
}
}
- const bool MergeShortRecord = [this, &NextLine]() {
- switch (Style.AllowShortRecordOnASingleLine) {
- case FormatStyle::SRS_Always:
- return true;
- case FormatStyle::SRS_Empty:
- return NextLine.First->is(tok::r_brace);
- case FormatStyle::SRS_Never:
- return false;
- }
- }() && !Style.BraceWrapping.SplitEmptyRecord;
-
// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord &&
@@ -503,6 +492,18 @@ class LineJoiner {
: 0;
}
+ const bool MergeShortRecord = [this, &NextLine]() {
+ switch (Style.AllowShortRecordOnASingleLine) {
+ case FormatStyle::SRS_Always:
+ return true;
+ case FormatStyle::SRS_EmptyIfAttached:
+ case FormatStyle::SRS_Empty:
+ return NextLine.First->is(tok::r_brace);
+ case FormatStyle::SRS_Never:
+ return false;
+ }
+ }();
+
if (TheLine->Last->is(tok::l_brace)) {
bool ShouldMerge = false;
// Try to merge records.
@@ -511,14 +512,15 @@ class LineJoiner {
} else if (TheLine->Last->is(TT_CompoundRequirementLBrace)) {
ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine;
} else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
- TT_RecordLBrace)) {
- // NOTE: We use AfterClass (whereas AfterStruct exists) for both classes
- // and structs, but it seems that wrapping is still handled correctly
- // elsewhere.
- ShouldMerge = !Style.BraceWrapping.AfterClass ||
- (NextLine.First->is(tok::r_brace) &&
- !Style.BraceWrapping.SplitEmptyRecord) ||
- MergeShortRecord;
+ TT_UnionLBrace)) {
+ if (Style.AllowShortRecordOnASingleLine > FormatStyle::SRS_Never) {
+ // NOTE: We use AfterClass (whereas AfterStruct exists) for both
+ // classes and structs, but it seems that wrapping is still handled
+ // correctly elsewhere.
+ ShouldMerge =
+ !Style.BraceWrapping.AfterClass ||
+ (MergeShortRecord && !Style.BraceWrapping.SplitEmptyRecord);
+ }
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct, Keywords.kw_record)) {
@@ -895,7 +897,7 @@ class LineJoiner {
!startsExternCBlock(Line)) {
// Merge short records only when requested.
if (isRecordLBrace(*Line.Last) &&
- Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never) {
+ Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Always) {
return 0;
}
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index f355d5cb0c6af..8d36169012f11 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -960,8 +960,8 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
bool IsEmptyBlock = NextToken.is(tok::r_brace);
bool WrapRecordAllowed =
- !(IsEmptyBlock &&
- Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) ||
+ !(IsEmptyBlock && Style.AllowShortRecordOnASingleLine >
+ FormatStyle::SRS_EmptyIfAttached) ||
Style.BraceWrapping.SplitEmptyRecord;
switch (Kind) {
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 3afaba3359e3b..c395e6f4f29f8 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -668,13 +668,15 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("AllowShortLambdasOnASingleLine: true",
AllowShortLambdasOnASingleLine, FormatStyle::SLS_All);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ CHECK_PARSE("AllowShortRecordOnASingleLine: Never",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_Never);
+ CHECK_PARSE("AllowShortRecordOnASingleLine: EmptyIfAttached",
+ AllowShortRecordOnASingleLine, FormatStyle::SRS_EmptyIfAttached);
CHECK_PARSE("AllowShortRecordOnASingleLine: Empty",
AllowShortRecordOnASingleLine, FormatStyle::SRS_Empty);
CHECK_PARSE("AllowShortRecordOnASingleLine: Always",
AllowShortRecordOnASingleLine, FormatStyle::SRS_Always);
- CHECK_PARSE("AllowShortRecordOnASingleLine: Never",
- AllowShortRecordOnASingleLine, FormatStyle::SRS_Never);
Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both;
CHECK_PARSE("SpaceAroundPointerQualifiers: Default",
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index f7fbc095c927f..ebeed67a75a03 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15370,14 +15370,87 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
Style);
}
-TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
+TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
FormatStyle Style = getLLVMStyle();
Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.SplitEmptyRecord = false;
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+
+ verifyFormat("class foo {\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo {\n};", Style);
+
+ verifyFormat("struct foo {\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo {\n};", Style);
+
+ verifyFormat("union foo {\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo {\n};", Style);
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+
+ verifyFormat("class foo {\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo {\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo {\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("union foo {};", Style);
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+
+ verifyFormat("class foo {\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo {\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo {\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo {};", Style);
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo { int bar; };", Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo { int bar; };", Style);
+ verifyFormat("union foo {};", Style);
+
Style.BraceWrapping.AfterClass = true;
Style.BraceWrapping.AfterStruct = true;
Style.BraceWrapping.AfterUnion = true;
- Style.BraceWrapping.SplitEmptyRecord = false;
+
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo\n{\n"
@@ -15398,16 +15471,25 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo\n{};", Style);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
- verifyFormat("class foo { void bar(); };", Style);
- verifyFormat("class foo {};", Style);
+ verifyFormat("class foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo\n{};", Style);
- verifyFormat("struct foo { int bar; };", Style);
- verifyFormat("struct foo {};", Style);
+ verifyFormat("struct foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("struct foo\n{};", Style);
- verifyFormat("union foo { int bar; };", Style);
- verifyFormat("union foo {};", Style);
+ verifyFormat("union foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("union foo\n{};", Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
@@ -15429,7 +15511,21 @@ TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
Style);
verifyFormat("union foo {};", Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo { int bar; };", Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo { int bar; };", Style);
+ verifyFormat("union foo {};", Style);
+
+ // Ensure option gets overriden by SplitEmptyRecord
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
Style.BraceWrapping.SplitEmptyRecord = true;
+
verifyFormat("class foo\n{\n}", Style);
verifyFormat("struct foo\n{\n}", Style);
verifyFormat("union foo\n{\n}", Style);
>From aeda7b8975d1f992b475e75386275ab37cde129b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Tue, 2 Sep 2025 21:11:56 +0200
Subject: [PATCH 09/25] Fix incorrect handling of left brace wrapping
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 36 ++++--
clang/lib/Format/UnwrappedLineParser.cpp | 3 +-
clang/unittests/Format/FormatTest.cpp | 124 +++++++-------------
3 files changed, 65 insertions(+), 98 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 4d7c703faab57..16cb37719436b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -266,6 +266,14 @@ class LineJoiner {
}
}
+ // Try merging record blocks that have had their left brace wrapped.
+ if (TheLine->First->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union) &&
+ NextLine.First->is(tok::l_brace) && NextLine.First == NextLine.Last &&
+ I + 2 != E && !I[2]->First->is(tok::r_brace)) {
+ if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
+ return MergedLines;
+ }
+
const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr;
// Handle empty record blocks where the brace has already been wrapped.
if (PreviousLine && TheLine->Last->is(tok::l_brace) &&
@@ -492,17 +500,17 @@ class LineJoiner {
: 0;
}
- const bool MergeShortRecord = [this, &NextLine]() {
+ const bool TryMergeShortRecord = [this, &NextLine]() {
switch (Style.AllowShortRecordOnASingleLine) {
- case FormatStyle::SRS_Always:
- return true;
+ case FormatStyle::SRS_Never:
+ return false;
case FormatStyle::SRS_EmptyIfAttached:
case FormatStyle::SRS_Empty:
return NextLine.First->is(tok::r_brace);
- case FormatStyle::SRS_Never:
- return false;
+ case FormatStyle::SRS_Always:
+ return true;
}
- }();
+ }() && !Style.BraceWrapping.SplitEmptyRecord;
if (TheLine->Last->is(tok::l_brace)) {
bool ShouldMerge = false;
@@ -517,9 +525,7 @@ class LineJoiner {
// NOTE: We use AfterClass (whereas AfterStruct exists) for both
// classes and structs, but it seems that wrapping is still handled
// correctly elsewhere.
- ShouldMerge =
- !Style.BraceWrapping.AfterClass ||
- (MergeShortRecord && !Style.BraceWrapping.SplitEmptyRecord);
+ ShouldMerge = !Style.BraceWrapping.AfterClass || TryMergeShortRecord;
}
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -952,9 +958,15 @@ class LineJoiner {
return 0;
Limit -= 2;
unsigned MergedLines = 0;
- if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never ||
- (I[1]->First == I[1]->Last && I + 2 != E &&
- I[2]->First->is(tok::r_brace))) {
+
+ bool TryMergeBlock =
+ Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never;
+ bool TryMergeRecord =
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always;
+ bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E &&
+ I[2]->First->is(tok::r_brace);
+
+ if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) {
MergedLines = tryMergeSimpleBlock(I + 1, E, Limit);
// If we managed to merge the block, count the statement header, which
// is on a separate line.
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 8d36169012f11..f177a15db0df0 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -4162,8 +4162,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always &&
- ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
IsJavaRecord)) {
addUnwrappedLine();
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index ebeed67a75a03..34489f529ec0e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15370,162 +15370,118 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
Style);
}
-TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
+TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
FormatStyle Style = getLLVMStyle();
Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.SplitEmptyRecord = false;
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
-
verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo {\n};", Style);
- verifyFormat("struct foo {\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("struct foo {\n};", Style);
-
- verifyFormat("union foo {\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("union foo {\n};", Style);
-
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
-
verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo {};", Style);
- verifyFormat("struct foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("struct foo {};", Style);
-
- verifyFormat("union foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("union foo {};", Style);
-
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
-
verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo {};", Style);
- verifyFormat("struct foo {\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("struct foo {};", Style);
-
- verifyFormat("union foo {\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("union foo {};", Style);
-
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
-
verifyFormat("class foo { void bar(); };", Style);
verifyFormat("class foo {};", Style);
- verifyFormat("struct foo { int bar; };", Style);
- verifyFormat("struct foo {};", Style);
-
- verifyFormat("union foo { int bar; };", Style);
- verifyFormat("union foo {};", Style);
-
Style.BraceWrapping.AfterClass = true;
Style.BraceWrapping.AfterStruct = true;
Style.BraceWrapping.AfterUnion = true;
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
-
verifyFormat("class foo\n{\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo\n{};", Style);
- verifyFormat("struct foo\n{\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("struct foo\n{};", Style);
-
- verifyFormat("union foo\n{\n"
- " int bar;\n"
- "};",
- Style);
- verifyFormat("union foo\n{};", Style);
-
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
-
verifyFormat("class foo\n{\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo\n{};", Style);
- verifyFormat("struct foo\n{\n"
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+ verifyFormat("class foo\n{\n"
" void bar();\n"
"};",
Style);
- verifyFormat("struct foo\n{};", Style);
+ verifyFormat("class foo {};", Style);
- verifyFormat("union foo\n{\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("union foo\n{};", Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+}
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) {
+ FormatStyle Style = getLLVMStyle();
- verifyFormat("class foo\n{\n"
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.SplitEmptyRecord = true;
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
- verifyFormat("class foo {};", Style);
+ verifyFormat("class foo {\n};", Style);
- verifyFormat("struct foo\n{\n"
- " int bar;\n"
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ verifyFormat("class foo {\n"
+ " void bar();\n"
"};",
Style);
- verifyFormat("struct foo {};", Style);
+ verifyFormat("class foo {};", Style);
- verifyFormat("union foo\n{\n"
- " int bar;\n"
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+ verifyFormat("class foo {\n"
+ " void bar();\n"
"};",
Style);
- verifyFormat("union foo {};", Style);
+ verifyFormat("class foo {};", Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
-
verifyFormat("class foo { void bar(); };", Style);
verifyFormat("class foo {};", Style);
- verifyFormat("struct foo { int bar; };", Style);
- verifyFormat("struct foo {};", Style);
+ Style.BraceWrapping.AfterClass = true;
+ Style.BraceWrapping.AfterStruct = true;
+ Style.BraceWrapping.AfterUnion = true;
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ verifyFormat("class foo\n{\n}", Style);
+ verifyFormat("struct foo\n{\n}", Style);
+ verifyFormat("union foo\n{\n}", Style);
- verifyFormat("union foo { int bar; };", Style);
- verifyFormat("union foo {};", Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ verifyFormat("class foo\n{\n}", Style);
+ verifyFormat("struct foo\n{\n}", Style);
+ verifyFormat("union foo\n{\n}", Style);
- // Ensure option gets overriden by SplitEmptyRecord
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- Style.BraceWrapping.SplitEmptyRecord = true;
+ verifyFormat("class foo\n{\n}", Style);
+ verifyFormat("struct foo\n{\n}", Style);
+ verifyFormat("union foo\n{\n}", Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
verifyFormat("class foo\n{\n}", Style);
verifyFormat("struct foo\n{\n}", Style);
verifyFormat("union foo\n{\n}", Style);
>From cbbce933f7854aa6d49ec786572aede8df75db4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 3 Sep 2025 17:49:31 +0200
Subject: [PATCH 10/25] Fixup test cases
---
clang/unittests/Format/FormatTest.cpp | 64 +++++++++++++++------------
1 file changed, 36 insertions(+), 28 deletions(-)
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 34489f529ec0e..493525fa5fdaf 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15371,7 +15371,7 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
}
TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
- FormatStyle Style = getLLVMStyle();
+ auto Style = getLLVMStyle();
Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.SplitEmptyRecord = false;
@@ -15381,7 +15381,9 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
" void bar();\n"
"};",
Style);
- verifyFormat("class foo {\n};", Style);
+ verifyFormat("class foo {\n"
+ "};",
+ Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
verifyFormat("class foo {\n"
@@ -15402,25 +15404,26 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
verifyFormat("class foo {};", Style);
Style.BraceWrapping.AfterClass = true;
- Style.BraceWrapping.AfterStruct = true;
- Style.BraceWrapping.AfterUnion = true;
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
- verifyFormat("class foo\n{\n"
+ verifyFormat("class foo\n"
+ "{\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo\n{};", Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
- verifyFormat("class foo\n{\n"
+ verifyFormat("class foo\n"
+ "{\n"
" void bar();\n"
"};",
Style);
verifyFormat("class foo\n{};", Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- verifyFormat("class foo\n{\n"
+ verifyFormat("class foo\n"
+ "{\n"
" void bar();\n"
"};",
Style);
@@ -15432,24 +15435,26 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
}
TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) {
- FormatStyle Style = getLLVMStyle();
+ auto Style = getLLVMStyle();
- Style.BreakBeforeBraces = FormatStyle::BS_Custom;
- Style.BraceWrapping.SplitEmptyRecord = true;
+ EXPECT_EQ(Style.BraceWrapping.SplitEmptyRecord, true);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ EXPECT_EQ(Style.AllowShortRecordOnASingleLine,
+ FormatStyle::SRS_EmptyIfAttached);
verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
- verifyFormat("class foo {\n};", Style);
+ verifyFormat("class foo {};", Style);
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo {\n"
" void bar();\n"
"};",
Style);
- verifyFormat("class foo {};", Style);
+ verifyFormat("class foo {\n"
+ "};",
+ Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
verifyFormat("class foo {\n"
@@ -15462,29 +15467,32 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) {
verifyFormat("class foo { void bar(); };", Style);
verifyFormat("class foo {};", Style);
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.AfterClass = true;
- Style.BraceWrapping.AfterStruct = true;
- Style.BraceWrapping.AfterUnion = true;
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
- verifyFormat("class foo\n{\n}", Style);
- verifyFormat("struct foo\n{\n}", Style);
- verifyFormat("union foo\n{\n}", Style);
+ verifyFormat("class foo\n"
+ "{\n"
+ "}",
+ Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
- verifyFormat("class foo\n{\n}", Style);
- verifyFormat("struct foo\n{\n}", Style);
- verifyFormat("union foo\n{\n}", Style);
+ verifyFormat("class foo\n"
+ "{\n"
+ "}",
+ Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- verifyFormat("class foo\n{\n}", Style);
- verifyFormat("struct foo\n{\n}", Style);
- verifyFormat("union foo\n{\n}", Style);
+ verifyFormat("class foo\n"
+ "{\n"
+ "}",
+ Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
- verifyFormat("class foo\n{\n}", Style);
- verifyFormat("struct foo\n{\n}", Style);
- verifyFormat("union foo\n{\n}", Style);
+ verifyFormat("class foo\n"
+ "{\n"
+ "}",
+ Style);
}
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
>From addc6420c5452d930fe4458b028a08e83abcce43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Wed, 3 Sep 2025 17:59:08 +0200
Subject: [PATCH 11/25] Fixup ShouldBreakBeforeBrace
---
clang/lib/Format/UnwrappedLineParser.cpp | 232 ++++++++++++-----------
1 file changed, 123 insertions(+), 109 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index f177a15db0df0..a8e9288cff4ef 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -949,7 +949,7 @@ static bool isIIFE(const UnwrappedLine &Line,
static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
const FormatToken &InitialToken,
- const FormatToken &NextToken,
+ const bool IsEmptyBlock,
const bool IsJavaRecord) {
if (IsJavaRecord)
return Style.BraceWrapping.AfterClass;
@@ -958,10 +958,9 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
if (InitialToken.is(TT_NamespaceMacro))
Kind = tok::kw_namespace;
- bool IsEmptyBlock = NextToken.is(tok::r_brace);
bool WrapRecordAllowed =
- !(IsEmptyBlock && Style.AllowShortRecordOnASingleLine >
- FormatStyle::SRS_EmptyIfAttached) ||
+ !IsEmptyBlock ||
+ Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Empty ||
Style.BraceWrapping.SplitEmptyRecord;
switch (Kind) {
@@ -3209,7 +3208,8 @@ void UnwrappedLineParser::parseNamespace() {
if (FormatTok->is(tok::l_brace)) {
FormatTok->setFinalizedType(TT_NamespaceLBrace);
- if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ if (ShouldBreakBeforeBrace(Style, InitialToken,
+ Tokens->peekNextToken()->is(tok::r_brace),
/*IsJavaRecord=*/false)) {
addUnwrappedLine();
}
@@ -3263,7 +3263,8 @@ void UnwrappedLineParser::parseNew() {
if (FormatTok->is(tok::l_paren)) {
parseParens();
- // If there is a class body of an anonymous class, consume that as child.
+ // If there is a class body of an anonymous class, consume that as
+ // child.
if (FormatTok->is(tok::l_brace))
parseChildBlock();
return;
@@ -3317,8 +3318,8 @@ void UnwrappedLineParser::parseForOrWhileLoop(bool HasParens) {
nextToken();
if (HasParens && FormatTok->is(tok::l_paren)) {
// The type is only set for Verilog basically because we were afraid to
- // change the existing behavior for loops. See the discussion on D121756 for
- // details.
+ // change the existing behavior for loops. See the discussion on D121756
+ // for details.
if (Style.isVerilog())
FormatTok->setFinalizedType(TT_ConditionLParen);
parseParens();
@@ -3476,7 +3477,8 @@ bool UnwrappedLineParser::parseRequires(bool SeenEqual) {
parseRequiresExpression();
return false;
case tok::l_paren:
- // Clauses and expression can start with a paren, it's unclear what we have.
+ // Clauses and expression can start with a paren, it's unclear what we
+ // have.
break;
default:
// All other tokens can only be a clause.
@@ -3539,8 +3541,8 @@ bool UnwrappedLineParser::parseRequires(bool SeenEqual) {
// Now we look forward and try to check if the paren content is a parameter
// list. The parameters can be cv-qualified and contain references or
// pointers.
- // So we want basically to check for TYPE NAME, but TYPE can contain all kinds
- // of stuff: typename, const, *, &, &&, ::, identifiers.
+ // So we want basically to check for TYPE NAME, but TYPE can contain all
+ // kinds of stuff: typename, const, *, &, &&, ::, identifiers.
unsigned StoredPosition = Tokens->getPosition();
FormatToken *NextToken = Tokens->getNextToken();
@@ -3612,8 +3614,8 @@ void UnwrappedLineParser::parseRequiresClause() {
assert(FormatTok->is(tok::kw_requires) && "'requires' expected");
// If there is no previous token, we are within a requires expression,
- // otherwise we will always have the template or function declaration in front
- // of it.
+ // otherwise we will always have the template or function declaration in
+ // front of it.
bool InRequiresExpression =
!FormatTok->Previous ||
FormatTok->Previous->is(TT_RequiresExpressionLBrace);
@@ -3658,21 +3660,21 @@ void UnwrappedLineParser::parseRequiresExpression() {
/// This is the body of a requires clause. It returns, when the parsing is
/// complete, or the expression is incorrect.
void UnwrappedLineParser::parseConstraintExpression() {
- // The special handling for lambdas is needed since tryToParseLambda() eats a
- // token and if a requires expression is the last part of a requires clause
- // and followed by an attribute like [[nodiscard]] the ClosesRequiresClause is
- // not set on the correct token. Thus we need to be aware if we even expect a
- // lambda to be possible.
- // template <typename T> requires requires { ... } [[nodiscard]] ...;
+ // The special handling for lambdas is needed since tryToParseLambda() eats
+ // a token and if a requires expression is the last part of a requires
+ // clause and followed by an attribute like [[nodiscard]] the
+ // ClosesRequiresClause is not set on the correct token. Thus we need to be
+ // aware if we even expect a lambda to be possible. template <typename T>
+ // requires requires { ... } [[nodiscard]] ...;
bool LambdaNextTimeAllowed = true;
- // Within lambda declarations, it is permitted to put a requires clause after
- // its template parameter list, which would place the requires clause right
- // before the parentheses of the parameters of the lambda declaration. Thus,
- // we track if we expect to see grouping parentheses at all.
- // Without this check, `requires foo<T> (T t)` in the below example would be
- // seen as the whole requires clause, accidentally eating the parameters of
- // the lambda.
+ // Within lambda declarations, it is permitted to put a requires clause
+ // after its template parameter list, which would place the requires clause
+ // right before the parentheses of the parameters of the lambda declaration.
+ // Thus, we track if we expect to see grouping parentheses at all. Without
+ // this check, `requires foo<T> (T t)` in the below example would be seen as
+ // the whole requires clause, accidentally eating the parameters of the
+ // lambda.
// [&]<typename T> requires foo<T> (T t) { ... };
bool TopLevelParensAllowed = true;
@@ -3805,9 +3807,9 @@ bool UnwrappedLineParser::parseEnum() {
if (FormatTok->is(tok::kw_enum))
nextToken();
- // In TypeScript, "enum" can also be used as property name, e.g. in interface
- // declarations. An "enum" keyword followed by a colon would be a syntax
- // error and thus assume it is just an identifier.
+ // In TypeScript, "enum" can also be used as property name, e.g. in
+ // interface declarations. An "enum" keyword followed by a colon would be a
+ // syntax error and thus assume it is just an identifier.
if (Style.isJavaScript() && FormatTok->isOneOf(tok::colon, tok::question))
return false;
@@ -3866,7 +3868,8 @@ bool UnwrappedLineParser::parseEnum() {
}
if (!Style.AllowShortEnumsOnASingleLine &&
- ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ ShouldBreakBeforeBrace(Style, InitialToken,
+ Tokens->peekNextToken()->is(tok::r_brace),
/*IsJavaRecord=*/false)) {
addUnwrappedLine();
}
@@ -4117,8 +4120,8 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
FormatToken *Previous = FormatTok->Previous;
if (!Previous || (Previous->isNot(tok::r_paren) &&
!Previous->isTypeOrIdentifier(LangOpts))) {
- // Don't try parsing a lambda if we had a closing parenthesis before,
- // it was probably a pointer to an array: int (*)[].
+ // Don't try parsing a lambda if we had a closing parenthesis
+ // before, it was probably a pointer to an array: int (*)[].
if (!tryToParseLambda())
continue;
} else {
@@ -4162,18 +4165,20 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken(),
+ if (ShouldBreakBeforeBrace(Style, InitialToken,
+ Tokens->peekNextToken()->is(tok::r_brace),
IsJavaRecord)) {
addUnwrappedLine();
}
unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
- parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
+ parseBlock(/*MustBeDeclaration=*/true, AddLevels,
+ /*MunchSemi=*/false);
}
setPreviousRBraceType(ClosingBraceType);
}
- // There is no addUnwrappedLine() here so that we fall through to parsing a
- // structural element afterwards. Thus, in "class A {} n, m;",
+ // There is no addUnwrappedLine() here so that we fall through to
+ // parsing a structural element afterwards. Thus, in "class A {} n, m;",
// "} n, m;" will end up in one unwrapped line.
}
@@ -4261,8 +4266,8 @@ void UnwrappedLineParser::parseObjCInterfaceOrImplementation() {
parseBlock(/*MustBeDeclaration=*/true);
}
- // With instance variables, this puts '}' on its own line. Without instance
- // variables, this ends the @interface line.
+ // With instance variables, this puts '}' on its own line. Without
+ // instance variables, this ends the @interface line.
addUnwrappedLine();
parseObjCUntilAtEnd();
@@ -4300,7 +4305,8 @@ bool UnwrappedLineParser::parseObjCProtocol() {
nextToken();
if (FormatTok->is(tok::l_paren)) {
- // The expression form of @protocol, e.g. "Protocol* p = @protocol(foo);".
+ // The expression form of @protocol, e.g. "Protocol* p =
+ // @protocol(foo);".
return false;
}
@@ -4335,9 +4341,9 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
if (FormatTok->is(tok::kw_default))
nextToken();
- // Consume "async function", "function" and "default function", so that these
- // get parsed as free-standing JS functions, i.e. do not require a trailing
- // semicolon.
+ // Consume "async function", "function" and "default function", so that
+ // these get parsed as free-standing JS functions, i.e. do not require a
+ // trailing semicolon.
if (FormatTok->is(Keywords.kw_async))
nextToken();
if (FormatTok->is(Keywords.kw_function)) {
@@ -4345,10 +4351,10 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
return;
}
- // For imports, `export *`, `export {...}`, consume the rest of the line up
- // to the terminating `;`. For everything else, just return and continue
- // parsing the structural element, i.e. the declaration or expression for
- // `export default`.
+ // For imports, `export *`, `export {...}`, consume the rest of the line
+ // up to the terminating `;`. For everything else, just return and
+ // continue parsing the structural element, i.e. the declaration or
+ // expression for `export default`.
if (!IsImport && FormatTok->isNoneOf(tok::l_brace, tok::star) &&
!FormatTok->isStringLiteral() &&
!(FormatTok->is(Keywords.kw_type) &&
@@ -4360,8 +4366,8 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
if (FormatTok->is(tok::semi))
return;
if (Line->Tokens.empty()) {
- // Common issue: Automatic Semicolon Insertion wrapped the line, so the
- // import statement should terminate.
+ // Common issue: Automatic Semicolon Insertion wrapped the line, so
+ // the import statement should terminate.
return;
}
if (FormatTok->is(tok::l_brace)) {
@@ -4540,11 +4546,11 @@ void UnwrappedLineParser::parseVerilogTable() {
}
void UnwrappedLineParser::parseVerilogCaseLabel() {
- // The label will get unindented in AnnotatingParser. If there are no leading
- // spaces, indent the rest here so that things inside the block will be
- // indented relative to things outside. We don't use parseLabel because we
- // don't know whether this colon is a label or a ternary expression at this
- // point.
+ // The label will get unindented in AnnotatingParser. If there are no
+ // leading spaces, indent the rest here so that things inside the block
+ // will be indented relative to things outside. We don't use parseLabel
+ // because we don't know whether this colon is a label or a ternary
+ // expression at this point.
auto OrigLevel = Line->Level;
auto FirstLine = CurrentLines->size();
if (Line->Level == 0 || (Line->InPPDirective && Line->Level <= 1))
@@ -4552,8 +4558,8 @@ void UnwrappedLineParser::parseVerilogCaseLabel() {
else if (!Style.IndentCaseBlocks && Keywords.isVerilogBegin(*FormatTok))
--Line->Level;
parseStructuralElement();
- // Restore the indentation in both the new line and the line that has the
- // label.
+ // Restore the indentation in both the new line and the line that has
+ // the label.
if (CurrentLines->size() > FirstLine)
(*CurrentLines)[FirstLine].Level = OrigLevel;
Line->Level = OrigLevel;
@@ -4598,24 +4604,24 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
});
// If this line closes a block when in Whitesmiths mode, remember that
- // information so that the level can be decreased after the line is added.
- // This has to happen after the addition of the line since the line itself
- // needs to be indented.
+ // information so that the level can be decreased after the line is
+ // added. This has to happen after the addition of the line since the
+ // line itself needs to be indented.
bool ClosesWhitesmithsBlock =
Line->MatchingOpeningBlockLineIndex != UnwrappedLine::kInvalidIndex &&
Style.BreakBeforeBraces == FormatStyle::BS_Whitesmiths;
// If the current line was expanded from a macro call, we use it to
- // reconstruct an unwrapped line from the structure of the expanded unwrapped
- // line and the unexpanded token stream.
+ // reconstruct an unwrapped line from the structure of the expanded
+ // unwrapped line and the unexpanded token stream.
if (!parsingPPDirective() && !InExpansion && containsExpansion(*Line)) {
if (!Reconstruct)
Reconstruct.emplace(Line->Level, Unexpanded);
Reconstruct->addLine(*Line);
// While the reconstructed unexpanded lines are stored in the normal
- // flow of lines, the expanded lines are stored on the side to be analyzed
- // in an extra step.
+ // flow of lines, the expanded lines are stored on the side to be
+ // analyzed in an extra step.
CurrentExpandedLines.push_back(std::move(*Line));
if (Reconstruct->finished()) {
@@ -4633,8 +4639,9 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
Reconstruct.reset();
}
} else {
- // At the top level we only get here when no unexpansion is going on, or
- // when conditional formatting led to unfinished macro reconstructions.
+ // At the top level we only get here when no unexpansion is going on,
+ // or when conditional formatting led to unfinished macro
+ // reconstructions.
assert(!Reconstruct || (CurrentLines != &Lines) || !PPStack.empty());
CurrentLines->push_back(std::move(*Line));
}
@@ -4652,7 +4659,8 @@ void UnwrappedLineParser::addUnwrappedLine(LineLevel AdjustLevel) {
std::make_move_iterator(PreprocessorDirectives.end()));
PreprocessorDirectives.clear();
}
- // Disconnect the current token from the last token on the previous line.
+ // Disconnect the current token from the last token on the previous
+ // line.
FormatTok->Previous = nullptr;
}
@@ -4663,8 +4671,8 @@ bool UnwrappedLineParser::isOnNewLine(const FormatToken &FormatTok) {
FormatTok.NewlinesBefore > 0;
}
-// Checks if \p FormatTok is a line comment that continues the line comment
-// section on \p Line.
+// Checks if \p FormatTok is a line comment that continues the line
+// comment section on \p Line.
static bool
continuesLineCommentSection(const FormatToken &FormatTok,
const UnwrappedLine &Line, const FormatStyle &Style,
@@ -4680,20 +4688,21 @@ continuesLineCommentSection(const FormatToken &FormatTok,
if (CommentPragmasRegex.match(IndentContent))
return false;
- // If Line starts with a line comment, then FormatTok continues the comment
- // section if its original column is greater or equal to the original start
- // column of the line.
+ // If Line starts with a line comment, then FormatTok continues the
+ // comment section if its original column is greater or equal to the
+ // original start column of the line.
//
- // Define the min column token of a line as follows: if a line ends in '{' or
- // contains a '{' followed by a line comment, then the min column token is
- // that '{'. Otherwise, the min column token of the line is the first token of
- // the line.
+ // Define the min column token of a line as follows: if a line ends in
+ // '{' or contains a '{' followed by a line comment, then the min column
+ // token is that '{'. Otherwise, the min column token of the line is the
+ // first token of the line.
//
// If Line starts with a token other than a line comment, then FormatTok
- // continues the comment section if its original column is greater than the
- // original start column of the min column token of the line.
+ // continues the comment section if its original column is greater than
+ // the original start column of the min column token of the line.
//
- // For example, the second line comment continues the first in these cases:
+ // For example, the second line comment continues the first in these
+ // cases:
//
// // first line
// // second line
@@ -4748,8 +4757,8 @@ continuesLineCommentSection(const FormatToken &FormatTok,
// };
const FormatToken *MinColumnToken = Line.Tokens.front().Tok;
- // Scan for '{//'. If found, use the column of '{' as a min column for line
- // comment section continuation.
+ // Scan for '{//'. If found, use the column of '{' as a min column for
+ // line comment section continuation.
const FormatToken *PreviousToken = nullptr;
for (const UnwrappedLineNode &Node : Line.Tokens) {
if (PreviousToken && PreviousToken->is(tok::l_brace) &&
@@ -4773,14 +4782,15 @@ continuesLineCommentSection(const FormatToken &FormatTok,
void UnwrappedLineParser::flushComments(bool NewlineBeforeNext) {
bool JustComments = Line->Tokens.empty();
for (FormatToken *Tok : CommentsBeforeNextToken) {
- // Line comments that belong to the same line comment section are put on the
- // same line since later we might want to reflow content between them.
- // Additional fine-grained breaking of line comment sections is controlled
- // by the class BreakableLineCommentSection in case it is desirable to keep
- // several line comment sections in the same unwrapped line.
+ // Line comments that belong to the same line comment section are put
+ // on the same line since later we might want to reflow content
+ // between them. Additional fine-grained breaking of line comment
+ // sections is controlled by the class BreakableLineCommentSection in
+ // case it is desirable to keep several line comment sections in the
+ // same unwrapped line.
//
- // FIXME: Consider putting separate line comment sections as children to the
- // unwrapped line instead.
+ // FIXME: Consider putting separate line comment sections as children
+ // to the unwrapped line instead.
Tok->ContinuesLineCommentSection =
continuesLineCommentSection(*Tok, *Line, Style, CommentPragmasRegex);
if (isOnNewLine(*Tok) && JustComments && !Tok->ContinuesLineCommentSection)
@@ -4804,12 +4814,12 @@ void UnwrappedLineParser::nextToken(int LevelDifference) {
readTokenWithJavaScriptASI();
FormatTok->Previous = Previous;
if (Style.isVerilog()) {
- // Blocks in Verilog can have `begin` and `end` instead of braces. For
+ // Blocks in Verilog can have `begin` and `end` instead of braces. For
// keywords like `begin`, we can't treat them the same as left braces
// because some contexts require one of them. For example structs use
- // braces and if blocks use keywords, and a left brace can occur in an if
- // statement, but it is not a block. For keywords like `end`, we simply
- // treat them the same as right braces.
+ // braces and if blocks use keywords, and a left brace can occur in an
+ // if statement, but it is not a block. For keywords like `end`, we
+ // simply treat them the same as right braces.
if (Keywords.isVerilogEnd(*FormatTok))
FormatTok->Tok.setKind(tok::r_brace);
}
@@ -4820,10 +4830,11 @@ void UnwrappedLineParser::distributeComments(
// Whether or not a line comment token continues a line is controlled by
// the method continuesLineCommentSection, with the following caveat:
//
- // Define a trail of Comments to be a nonempty proper postfix of Comments such
- // that each comment line from the trail is aligned with the next token, if
- // the next token exists. If a trail exists, the beginning of the maximal
- // trail is marked as a start of a new comment section.
+ // Define a trail of Comments to be a nonempty proper postfix of
+ // Comments such that each comment line from the trail is aligned with
+ // the next token, if the next token exists. If a trail exists, the
+ // beginning of the maximal trail is marked as a start of a new comment
+ // section.
//
// For example in this code:
//
@@ -4832,9 +4843,9 @@ void UnwrappedLineParser::distributeComments(
// // line 2 about b
// int b;
//
- // the two lines about b form a maximal trail, so there are two sections, the
- // first one consisting of the single comment "// line about a" and the
- // second one consisting of the next two comments.
+ // the two lines about b form a maximal trail, so there are two
+ // sections, the first one consisting of the single comment "// line
+ // about a" and the second one consisting of the next two comments.
if (Comments.empty())
return;
bool ShouldPushCommentsInCurrentLine = true;
@@ -4895,8 +4906,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
return Tok.HasUnescapedNewline || Tok.IsFirst;
};
- // Consider preprocessor directives preceded by block comments as first
- // on line.
+ // Consider preprocessor directives preceded by block comments as
+ // first on line.
if (PreviousWasComment)
return FirstNonCommentOnLine || IsFirstOnLine(Tok);
return IsFirstOnLine(Tok);
@@ -4908,8 +4919,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
while (!Line->InPPDirective && FormatTok->is(tok::hash) &&
FirstNonCommentOnLine) {
- // In Verilog, the backtick is used for macro invocations. In TableGen,
- // the single hash is used for the paste operator.
+ // In Verilog, the backtick is used for macro invocations. In
+ // TableGen, the single hash is used for the paste operator.
const auto *Next = Tokens->peekNextToken();
if ((Style.isVerilog() && !Keywords.isVerilogPPDirective(*Next)) ||
(Style.isTableGen() &&
@@ -4919,17 +4930,19 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
}
distributeComments(Comments, FormatTok);
Comments.clear();
- // If there is an unfinished unwrapped line, we flush the preprocessor
- // directives only after that unwrapped line was finished later.
+ // If there is an unfinished unwrapped line, we flush the
+ // preprocessor directives only after that unwrapped line was
+ // finished later.
bool SwitchToPreprocessorLines = !Line->Tokens.empty();
ScopedLineState BlockState(*this, SwitchToPreprocessorLines);
assert((LevelDifference >= 0 ||
static_cast<unsigned>(-LevelDifference) <= Line->Level) &&
"LevelDifference makes Line->Level negative");
Line->Level += LevelDifference;
- // Comments stored before the preprocessor directive need to be output
- // before the preprocessor directive, at the same level as the
- // preprocessor directive, as we consider them to apply to the directive.
+ // Comments stored before the preprocessor directive need to be
+ // output before the preprocessor directive, at the same level as
+ // the preprocessor directive, as we consider them to apply to the
+ // directive.
if (Style.IndentPPDirectives == FormatStyle::PPDIS_BeforeHash &&
PPBranchLevel > 0) {
Line->Level += PPBranchLevel;
@@ -4955,8 +4968,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
FormatToken *ID = FormatTok;
unsigned Position = Tokens->getPosition();
- // To correctly parse the code, we need to replace the tokens of the macro
- // call with its expansion.
+ // To correctly parse the code, we need to replace the tokens of the
+ // macro call with its expansion.
auto PreCall = std::move(Line);
Line.reset(new UnwrappedLine);
bool OldInExpansion = InExpansion;
@@ -4985,7 +4998,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
!Macros.hasArity(ID->TokenText, Args->size())) {
// The macro is either
// - object-like, but we got argumnets, or
- // - overloaded to be both object-like and function-like, but none of
+ // - overloaded to be both object-like and function-like, but none
+ // of
// the function-like arities match the number of arguments.
// Thus, expand as object-like macro.
LLVM_DEBUG(llvm::dbgs()
>From 63048a7c5cc1cf5c0fab4b297cf0bdc5145a4219 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Thu, 4 Sep 2025 16:08:04 +0200
Subject: [PATCH 12/25] Fixups
---
clang/lib/Format/TokenAnnotator.cpp | 1 +
clang/lib/Format/UnwrappedLineFormatter.cpp | 13 +-
clang/unittests/Format/FormatTest.cpp | 139 +++++---------------
3 files changed, 40 insertions(+), 113 deletions(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index efd2a35a79bcd..255fa50dd560c 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5978,6 +5978,7 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
}
// Don't attempt to interpret record return types as records.
+ // FIXME: Not covered by tests.
if (Right.isNot(TT_FunctionLBrace)) {
return ((Line.startsWith(tok::kw_class) &&
Style.BraceWrapping.AfterClass) ||
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 16cb37719436b..438d91add39d1 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -267,9 +267,10 @@ class LineJoiner {
}
// Try merging record blocks that have had their left brace wrapped.
- if (TheLine->First->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union) &&
- NextLine.First->is(tok::l_brace) && NextLine.First == NextLine.Last &&
- I + 2 != E && !I[2]->First->is(tok::r_brace)) {
+ if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
+ TT_UnionLBrace) &&
+ NextLine.First == NextLine.Last && I + 2 != E &&
+ !I[2]->First->is(tok::r_brace)) {
if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
return MergedLines;
}
@@ -500,7 +501,7 @@ class LineJoiner {
: 0;
}
- const bool TryMergeShortRecord = [this, &NextLine]() {
+ const bool TryMergeShortRecord = [&]() {
switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Never:
return false;
@@ -521,7 +522,7 @@ class LineJoiner {
ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine;
} else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace)) {
- if (Style.AllowShortRecordOnASingleLine > FormatStyle::SRS_Never) {
+ if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) {
// NOTE: We use AfterClass (whereas AfterStruct exists) for both
// classes and structs, but it seems that wrapping is still handled
// correctly elsewhere.
@@ -903,7 +904,7 @@ class LineJoiner {
!startsExternCBlock(Line)) {
// Merge short records only when requested.
if (isRecordLBrace(*Line.Last) &&
- Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Always) {
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) {
return 0;
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 493525fa5fdaf..32382007b3b41 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8655,19 +8655,6 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) {
Style);
}
-TEST_F(FormatTest, BreakFunctionsReturningRecords) {
- FormatStyle Style = getLLVMStyle();
- Style.BreakBeforeBraces = FormatStyle::BS_Custom;
- Style.BraceWrapping.AfterFunction = true;
- Style.BraceWrapping.AfterClass = false;
- Style.BraceWrapping.AfterStruct = false;
- Style.BraceWrapping.AfterUnion = false;
-
- verifyFormat("class Bar foo() {}", Style);
- verifyFormat("struct Bar foo() {}", Style);
- verifyFormat("union Bar foo() {}", Style);
-}
-
TEST_F(FormatTest, DontBreakBeforeQualifiedOperator) {
// Regression test for https://bugs.llvm.org/show_bug.cgi?id=40516:
// Prefer keeping `::` followed by `operator` together.
@@ -15370,129 +15357,67 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
Style);
}
-TEST_F(FormatTest, AllowShortRecordOnASingleLineNonSplit) {
+TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
auto Style = getLLVMStyle();
-
- Style.BreakBeforeBraces = FormatStyle::BS_Custom;
- Style.BraceWrapping.SplitEmptyRecord = false;
+ EXPECT_EQ(Style.AllowShortRecordOnASingleLine,
+ FormatStyle::SRS_EmptyIfAttached);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo {\n"
- "};",
- Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
- verifyFormat("class foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo {};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- verifyFormat("class foo {\n"
- " void bar();\n"
+ "};\n"
+ "class bar {\n"
+ " int i;\n"
"};",
Style);
- verifyFormat("class foo {};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
- verifyFormat("class foo { void bar(); };", Style);
- verifyFormat("class foo {};", Style);
-
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.AfterClass = true;
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
verifyFormat("class foo\n"
"{\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo\n{};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
- verifyFormat("class foo\n"
+ "};\n"
+ "class bar\n"
"{\n"
- " void bar();\n"
+ " int i;\n"
"};",
Style);
- verifyFormat("class foo\n{};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+ Style.BraceWrapping.SplitEmptyRecord = false;
verifyFormat("class foo\n"
- "{\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo {};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
- verifyFormat("class foo { void bar(); };", Style);
- verifyFormat("class foo {};", Style);
-}
-
-TEST_F(FormatTest, AllowShortRecordOnASingleLineSplit) {
- auto Style = getLLVMStyle();
-
- EXPECT_EQ(Style.BraceWrapping.SplitEmptyRecord, true);
-
- EXPECT_EQ(Style.AllowShortRecordOnASingleLine,
- FormatStyle::SRS_EmptyIfAttached);
- verifyFormat("class foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo {};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
- verifyFormat("class foo {\n"
- " void bar();\n"
- "};",
- Style);
- verifyFormat("class foo {\n"
- "};",
+ "{};",
Style);
+ Style = getLLVMStyle();
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- verifyFormat("class foo {\n"
- " void bar();\n"
+ verifyFormat("class foo {};\n"
+ "class bar {\n"
+ " int i;\n"
"};",
Style);
- verifyFormat("class foo {};", Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
- verifyFormat("class foo { void bar(); };", Style);
- verifyFormat("class foo {};", Style);
-
Style.BreakBeforeBraces = FormatStyle::BS_Custom;
Style.BraceWrapping.AfterClass = true;
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
- verifyFormat("class foo\n"
- "{\n"
- "}",
- Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
verifyFormat("class foo\n"
"{\n"
- "}",
- Style);
-
- Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
- verifyFormat("class foo\n"
+ "};\n"
+ "class bar\n"
"{\n"
- "}",
+ " int i;\n"
+ "};",
Style);
+ Style.BraceWrapping.SplitEmptyRecord = false;
+ verifyFormat("class foo {};", Style);
+ Style = getLLVMStyle();
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+ verifyFormat("class foo {};\n"
+ "class bar { int i; };",
+ Style);
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterClass = true;
verifyFormat("class foo\n"
"{\n"
- "}",
+ "};\n"
+ "class bar { int i; };",
Style);
+ Style.BraceWrapping.SplitEmptyRecord = false;
+ verifyFormat("class foo {};", Style);
}
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
>From b6af146cf90a8af87dc290e2223129c4cfd0ceae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 5 Sep 2025 17:02:45 +0200
Subject: [PATCH 13/25] Update release notes, fixup UnwrappedLineFormatter
---
clang/docs/ReleaseNotes.rst | 1 +
clang/lib/Format/UnwrappedLineFormatter.cpp | 16 +++++++++-------
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4cfcd37df1866..43758a11463d8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -707,6 +707,7 @@ clang-format
- Rename ``(Binary|Decimal|Hex)MinDigits`` to ``...MinDigitsInsert`` and add
``(Binary|Decimal|Hex)MaxDigitsSeparator`` suboptions to
``IntegerLiteralSeparator``.
+- Add ``AllowShortRecordOnASingleLine`` option and set it to ``EmptyIfAttached`` for LLVM style.
libclang
--------
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 438d91add39d1..9f481c480dee7 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -501,7 +501,7 @@ class LineJoiner {
: 0;
}
- const bool TryMergeShortRecord = [&]() {
+ auto TryMergeShortRecord = [&]() {
switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Never:
return false;
@@ -511,7 +511,7 @@ class LineJoiner {
case FormatStyle::SRS_Always:
return true;
}
- }() && !Style.BraceWrapping.SplitEmptyRecord;
+ };
if (TheLine->Last->is(tok::l_brace)) {
bool ShouldMerge = false;
@@ -526,7 +526,9 @@ class LineJoiner {
// NOTE: We use AfterClass (whereas AfterStruct exists) for both
// classes and structs, but it seems that wrapping is still handled
// correctly elsewhere.
- ShouldMerge = !Style.BraceWrapping.AfterClass || TryMergeShortRecord;
+ ShouldMerge =
+ !Style.BraceWrapping.AfterClass ||
+ (TryMergeShortRecord() && !Style.BraceWrapping.SplitEmptyRecord);
}
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -960,12 +962,12 @@ class LineJoiner {
Limit -= 2;
unsigned MergedLines = 0;
- bool TryMergeBlock =
+ const bool TryMergeBlock =
Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never;
- bool TryMergeRecord =
+ const bool TryMergeRecord =
Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always;
- bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E &&
- I[2]->First->is(tok::r_brace);
+ const bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E &&
+ I[2]->First->is(tok::r_brace);
if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) {
MergedLines = tryMergeSimpleBlock(I + 1, E, Limit);
>From 4715a10ee13717933ea39f1bb50ebe490c375a44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 7 Sep 2025 13:21:26 +0200
Subject: [PATCH 14/25] Fixup FormatStyle::operator==, misc
UnwrappedLineFormatter
---
clang/include/clang/Format/Format.h | 1 +
clang/lib/Format/UnwrappedLineFormatter.cpp | 26 +++++++++++++--------
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 3ad8301342fb6..add6b5fdb3867 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5708,6 +5708,7 @@ struct FormatStyle {
AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine &&
AllowShortNamespacesOnASingleLine ==
R.AllowShortNamespacesOnASingleLine &&
+ AllowShortRecordOnASingleLine == R.AllowShortRecordOnASingleLine &&
AlwaysBreakBeforeMultilineStrings ==
R.AlwaysBreakBeforeMultilineStrings &&
AttributeMacros == R.AttributeMacros &&
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 9f481c480dee7..ca0d5bc9428f6 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -501,7 +501,7 @@ class LineJoiner {
: 0;
}
- auto TryMergeShortRecord = [&]() {
+ auto TryMergeShortRecord = [&] {
switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Never:
return false;
@@ -528,7 +528,7 @@ class LineJoiner {
// correctly elsewhere.
ShouldMerge =
!Style.BraceWrapping.AfterClass ||
- (TryMergeShortRecord() && !Style.BraceWrapping.SplitEmptyRecord);
+ (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord());
}
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
@@ -905,7 +905,11 @@ class LineJoiner {
} else if (Limit != 0 && !Line.startsWithNamespace() &&
!startsExternCBlock(Line)) {
// Merge short records only when requested.
- if (isRecordLBrace(*Line.Last) &&
+ if (Line.Last->isOneOf(TT_EnumLBrace, TT_RecordLBrace))
+ return 0;
+
+ if (Line.Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
+ TT_UnionLBrace) &&
Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) {
return 0;
}
@@ -962,14 +966,16 @@ class LineJoiner {
Limit -= 2;
unsigned MergedLines = 0;
- const bool TryMergeBlock =
- Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never;
- const bool TryMergeRecord =
- Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always;
- const bool NextIsEmptyBlock = I[1]->First == I[1]->Last && I + 2 != E &&
- I[2]->First->is(tok::r_brace);
+ auto TryMergeBlock = [&] {
+ if (Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never ||
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
+ return true;
+ }
+ return I[1]->First == I[1]->Last && I + 2 != E &&
+ I[2]->First->is(tok::r_brace);
+ };
- if (TryMergeBlock || TryMergeRecord || NextIsEmptyBlock) {
+ if (TryMergeBlock()) {
MergedLines = tryMergeSimpleBlock(I + 1, E, Limit);
// If we managed to merge the block, count the statement header, which
// is on a separate line.
>From 2e40b13c108b7cbed1209548168acac536a3b81a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 01:28:12 +0200
Subject: [PATCH 15/25] Fix interaction between AllowShortRecord and
AllowShortBlocks options
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 13 ++++++++-----
clang/unittests/Format/FormatTest.cpp | 3 +++
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index ca0d5bc9428f6..20df5c139a103 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -270,7 +270,8 @@ class LineJoiner {
if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace) &&
NextLine.First == NextLine.Last && I + 2 != E &&
- !I[2]->First->is(tok::r_brace)) {
+ !I[2]->First->is(tok::r_brace) &&
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) {
if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
return MergedLines;
}
@@ -279,7 +280,7 @@ class LineJoiner {
// Handle empty record blocks where the brace has already been wrapped.
if (PreviousLine && TheLine->Last->is(tok::l_brace) &&
TheLine->First == TheLine->Last) {
- bool EmptyBlock = NextLine.First->is(tok::r_brace);
+ const bool EmptyBlock = NextLine.First->is(tok::r_brace);
const FormatToken *Tok = PreviousLine->First;
if (Tok && Tok->is(tok::comment))
@@ -294,9 +295,10 @@ class LineJoiner {
if (Tok && Tok->is(tok::kw_typedef))
Tok = Tok->getNextNonComment();
if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union,
- tok::kw_extern, Keywords.kw_interface,
- Keywords.kw_record)) {
- return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
+ tok::kw_extern, Keywords.kw_interface)) {
+ return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
+ (!EmptyBlock && Style.AllowShortBlocksOnASingleLine ==
+ FormatStyle::SBS_Always)
? tryMergeSimpleBlock(I, E, Limit)
: 0;
}
@@ -910,6 +912,7 @@ class LineJoiner {
if (Line.Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace) &&
+ Line.Last != Line.First &&
Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) {
return 0;
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 32382007b3b41..82d87ee451616 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15383,6 +15383,9 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
verifyFormat("class foo\n"
"{};",
Style);
+ Style.BraceWrapping.SplitEmptyRecord = true;
+ Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always;
+ verifyFormat("class foo\n{ int i; };", Style);
Style = getLLVMStyle();
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
>From 05cf2aeadc587c4a89c5ecd2bd6266d1691b90de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 10:27:25 +0200
Subject: [PATCH 16/25] Fix incorrect merge check
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 7 +++----
clang/unittests/Format/FormatTest.cpp | 19 ++++++++++++++++---
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 20df5c139a103..5e82c8d3b3bce 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -271,7 +271,7 @@ class LineJoiner {
TT_UnionLBrace) &&
NextLine.First == NextLine.Last && I + 2 != E &&
!I[2]->First->is(tok::r_brace) &&
- Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) {
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
return MergedLines;
}
@@ -282,9 +282,7 @@ class LineJoiner {
TheLine->First == TheLine->Last) {
const bool EmptyBlock = NextLine.First->is(tok::r_brace);
- const FormatToken *Tok = PreviousLine->First;
- if (Tok && Tok->is(tok::comment))
- Tok = Tok->getNextNonComment();
+ const FormatToken *Tok = PreviousLine->getFirstNonComment();
if (Tok && Tok->getNamespaceToken()) {
return !Style.BraceWrapping.SplitEmptyNamespace && EmptyBlock
@@ -294,6 +292,7 @@ class LineJoiner {
if (Tok && Tok->is(tok::kw_typedef))
Tok = Tok->getNextNonComment();
+
if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union,
tok::kw_extern, Keywords.kw_interface)) {
return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 82d87ee451616..997238893f86e 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15383,9 +15383,6 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
verifyFormat("class foo\n"
"{};",
Style);
- Style.BraceWrapping.SplitEmptyRecord = true;
- Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always;
- verifyFormat("class foo\n{ int i; };", Style);
Style = getLLVMStyle();
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
@@ -15421,6 +15418,22 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
Style);
Style.BraceWrapping.SplitEmptyRecord = false;
verifyFormat("class foo {};", Style);
+
+ Style = getLLVMStyle();
+ Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always;
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterClass = true;
+
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Never;
+ verifyFormat("class foo\n"
+ "{ int i; };",
+ Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Empty;
+ verifyFormat("class foo\n"
+ "{ int i; };",
+ Style);
+ Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
+ verifyFormat("class foo { int i; };", Style);
}
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
>From 9b93306e4af64eee4aec52eafed0e86e902b184a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 8 Sep 2025 23:05:13 +0200
Subject: [PATCH 17/25] Add const, change is to isNot
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 2 +-
clang/lib/Format/UnwrappedLineParser.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 5e82c8d3b3bce..2b84298f04c4d 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -270,7 +270,7 @@ class LineJoiner {
if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace) &&
NextLine.First == NextLine.Last && I + 2 != E &&
- !I[2]->First->is(tok::r_brace) &&
+ I[2]->First->isNot(tok::r_brace) &&
Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
return MergedLines;
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index a8e9288cff4ef..9dd13f630eb29 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -958,7 +958,7 @@ static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
if (InitialToken.is(TT_NamespaceMacro))
Kind = tok::kw_namespace;
- bool WrapRecordAllowed =
+ const bool WrapRecordAllowed =
!IsEmptyBlock ||
Style.AllowShortRecordOnASingleLine < FormatStyle::SRS_Empty ||
Style.BraceWrapping.SplitEmptyRecord;
>From 01d13f1008ea2059179eced53bfe4dcd1e6c1081 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 15 Sep 2025 17:44:12 +0200
Subject: [PATCH 18/25] Extract record merging into a separate function
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 111 ++++++++++++++------
1 file changed, 79 insertions(+), 32 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 2b84298f04c4d..67f4c08a8d020 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -266,18 +266,17 @@ class LineJoiner {
}
}
- // Try merging record blocks that have had their left brace wrapped.
+ // Try merging record blocks that have had their left brace wrapped into
+ // a single line.
if (NextLine.First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
- TT_UnionLBrace) &&
- NextLine.First == NextLine.Last && I + 2 != E &&
- I[2]->First->isNot(tok::r_brace) &&
- Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
- if (unsigned MergedLines = tryMergeSimpleBlock(I, E, Limit))
+ TT_UnionLBrace)) {
+ if (unsigned MergedLines = tryMergeRecord(I, E, Limit))
return MergedLines;
}
const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr;
- // Handle empty record blocks where the brace has already been wrapped.
+
+ // Handle blocks where the brace has already been wrapped.
if (PreviousLine && TheLine->Last->is(tok::l_brace) &&
TheLine->First == TheLine->Last) {
const bool EmptyBlock = NextLine.First->is(tok::r_brace);
@@ -293,11 +292,11 @@ class LineJoiner {
if (Tok && Tok->is(tok::kw_typedef))
Tok = Tok->getNextNonComment();
- if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union,
- tok::kw_extern, Keywords.kw_interface)) {
- return (EmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
- (!EmptyBlock && Style.AllowShortBlocksOnASingleLine ==
- FormatStyle::SBS_Always)
+ if (Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union))
+ return tryMergeRecord(I, E, Limit);
+
+ if (Tok && Tok->isOneOf(tok::kw_extern, Keywords.kw_interface)) {
+ return !Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
? tryMergeSimpleBlock(I, E, Limit)
: 0;
}
@@ -502,18 +501,6 @@ class LineJoiner {
: 0;
}
- auto TryMergeShortRecord = [&] {
- switch (Style.AllowShortRecordOnASingleLine) {
- case FormatStyle::SRS_Never:
- return false;
- case FormatStyle::SRS_EmptyIfAttached:
- case FormatStyle::SRS_Empty:
- return NextLine.First->is(tok::r_brace);
- case FormatStyle::SRS_Always:
- return true;
- }
- };
-
if (TheLine->Last->is(tok::l_brace)) {
bool ShouldMerge = false;
// Try to merge records.
@@ -523,14 +510,7 @@ class LineJoiner {
ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine;
} else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace)) {
- if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never) {
- // NOTE: We use AfterClass (whereas AfterStruct exists) for both
- // classes and structs, but it seems that wrapping is still handled
- // correctly elsewhere.
- ShouldMerge =
- !Style.BraceWrapping.AfterClass ||
- (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord());
- }
+ return tryMergeRecord(I, E, Limit);
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct, Keywords.kw_record)) {
@@ -600,6 +580,73 @@ class LineJoiner {
return 0;
}
+ unsigned tryMergeRecord(ArrayRef<AnnotatedLine *>::const_iterator I,
+ ArrayRef<AnnotatedLine *>::const_iterator E,
+ unsigned Limit) {
+ const auto *Line = I[0];
+ const auto *NextLine = I[1];
+
+ auto GetRelevantAfterOption = [&](const FormatToken *tok) {
+ switch (tok->getType()) {
+ case TT_StructLBrace:
+ return Style.BraceWrapping.AfterStruct;
+ case TT_ClassLBrace:
+ return Style.BraceWrapping.AfterClass;
+ case TT_UnionLBrace:
+ return Style.BraceWrapping.AfterUnion;
+ };
+ };
+
+ // Current line begins both record and block, brace was not wrapped.
+ if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) {
+ auto TryMergeShortRecord = [&] {
+ switch (Style.AllowShortRecordOnASingleLine) {
+ case FormatStyle::SRS_EmptyIfAttached:
+ case FormatStyle::SRS_Empty:
+ return NextLine->First->is(tok::r_brace);
+ case FormatStyle::SRS_Always:
+ return true;
+ }
+ };
+
+ if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never &&
+ (!GetRelevantAfterOption(Line->Last) ||
+ (!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()))) {
+ return tryMergeSimpleBlock(I, E, Limit);
+ }
+ }
+
+ // Cases where the l_brace was wrapped.
+ // Current line begins record, next line block.
+ if (NextLine->First->isOneOf(TT_StructLBrace, TT_ClassLBrace,
+ TT_UnionLBrace)) {
+ if (NextLine->First == NextLine->Last && I + 2 != E &&
+ I[2]->First->isNot(tok::r_brace) &&
+ Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
+ return tryMergeSimpleBlock(I, E, Limit);
+ }
+ }
+
+ if (I == AnnotatedLines.begin())
+ return 0;
+
+ const auto *PreviousLine = I[-1];
+
+ // Previous line begins record, current line block.
+ if (PreviousLine->First->isOneOf(tok::kw_struct, tok::kw_class,
+ tok::kw_union)) {
+ const bool IsEmptyBlock =
+ Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
+
+ if (IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord ||
+ Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) {
+ return tryMergeSimpleBlock(I, E, Limit);
+ }
+ }
+
+ return 0;
+ }
+
unsigned
tryMergeSimplePPDirective(ArrayRef<AnnotatedLine *>::const_iterator I,
ArrayRef<AnnotatedLine *>::const_iterator E,
>From 49f13af929bbfaf77f671b2d899b7675ee2793ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Mon, 15 Sep 2025 18:48:00 +0200
Subject: [PATCH 19/25] Minor fixes for tryMergeRecord
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 67f4c08a8d020..6b2b5720b1dc2 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,6 +10,7 @@
#include "FormatToken.h"
#include "NamespaceEndCommentsFixer.h"
#include "WhitespaceManager.h"
+#include "clang/Basic/TokenKinds.h"
#include "llvm/Support/Debug.h"
#include <queue>
@@ -586,8 +587,8 @@ class LineJoiner {
const auto *Line = I[0];
const auto *NextLine = I[1];
- auto GetRelevantAfterOption = [&](const FormatToken *tok) {
- switch (tok->getType()) {
+ auto GetRelevantAfterOption = [&](const FormatToken *Tok) {
+ switch (Tok->getType()) {
case TT_StructLBrace:
return Style.BraceWrapping.AfterStruct;
case TT_ClassLBrace:
@@ -620,8 +621,12 @@ class LineJoiner {
// Current line begins record, next line block.
if (NextLine->First->isOneOf(TT_StructLBrace, TT_ClassLBrace,
TT_UnionLBrace)) {
- if (NextLine->First == NextLine->Last && I + 2 != E &&
- I[2]->First->isNot(tok::r_brace) &&
+ if (I + 2 == E)
+ return 0;
+
+ const bool IsEmptyBlock = I[2]->First->is(tok::r_brace);
+
+ if (!IsEmptyBlock &&
Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
return tryMergeSimpleBlock(I, E, Limit);
}
>From acc2856d3450b914adb225c76e5eaaae11546fe2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Thu, 18 Sep 2025 21:32:35 +0200
Subject: [PATCH 20/25] Fix -Wswitch and -Wlogical-op-parentheses errors
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 6b2b5720b1dc2..f60867a36fc65 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -595,6 +595,8 @@ class LineJoiner {
return Style.BraceWrapping.AfterClass;
case TT_UnionLBrace:
return Style.BraceWrapping.AfterUnion;
+ default:
+ return false;
};
};
@@ -602,6 +604,8 @@ class LineJoiner {
if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) {
auto TryMergeShortRecord = [&] {
switch (Style.AllowShortRecordOnASingleLine) {
+ case FormatStyle::SRS_Never:
+ return false;
case FormatStyle::SRS_EmptyIfAttached:
case FormatStyle::SRS_Empty:
return NextLine->First->is(tok::r_brace);
@@ -643,7 +647,7 @@ class LineJoiner {
const bool IsEmptyBlock =
Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
- if (IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord ||
+ if ((IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) {
return tryMergeSimpleBlock(I, E, Limit);
}
>From a60828a59cac6afa90f28e79453ecbdea9dd5c09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 19 Sep 2025 16:52:11 +0200
Subject: [PATCH 21/25] Fixup comments
---
clang/lib/Format/Format.cpp | 2 +-
clang/lib/Format/TokenAnnotator.cpp | 13 ++++++-------
clang/lib/Format/UnwrappedLineFormatter.cpp | 1 -
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 750c642ae0db4..1c8f366f05b06 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1684,9 +1684,9 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
- LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
+ LLVMStyle.AllowShortRecordOnASingleLine = FormatStyle::SRS_EmptyIfAttached;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
LLVMStyle.AlwaysBreakBeforeMultilineStrings = false;
LLVMStyle.AttributeMacros.push_back("__capability");
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 255fa50dd560c..9458c396679e0 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5980,13 +5980,12 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
// Don't attempt to interpret record return types as records.
// FIXME: Not covered by tests.
if (Right.isNot(TT_FunctionLBrace)) {
- return ((Line.startsWith(tok::kw_class) &&
- Style.BraceWrapping.AfterClass) ||
- (Line.startsWith(tok::kw_struct) &&
- Style.BraceWrapping.AfterStruct) ||
- (Line.startsWith(tok::kw_union) &&
- Style.BraceWrapping.AfterUnion)) &&
- Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never;
+ return Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never &&
+ (Line.startsWith(tok::kw_class) &&
+ Style.BraceWrapping.AfterClass) ||
+ (Line.startsWith(tok::kw_struct) &&
+ Style.BraceWrapping.AfterStruct) ||
+ (Line.startsWith(tok::kw_union) && Style.BraceWrapping.AfterUnion);
}
}
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f60867a36fc65..d3a25dc73224b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,7 +10,6 @@
#include "FormatToken.h"
#include "NamespaceEndCommentsFixer.h"
#include "WhitespaceManager.h"
-#include "clang/Basic/TokenKinds.h"
#include "llvm/Support/Debug.h"
#include <queue>
>From b53f14124c802ffec1af89d41d35d20835062e25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 19 Sep 2025 16:59:52 +0200
Subject: [PATCH 22/25] Fix missing empty block check
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 3 ++-
clang/unittests/Format/FormatTest.cpp | 6 +++++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index d3a25dc73224b..c3dde162e2671 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -647,7 +647,8 @@ class LineJoiner {
Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
if ((IsEmptyBlock && !Style.BraceWrapping.SplitEmptyRecord) ||
- Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always) {
+ (!IsEmptyBlock &&
+ Style.AllowShortBlocksOnASingleLine == FormatStyle::SBS_Always)) {
return tryMergeSimpleBlock(I, E, Limit);
}
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 997238893f86e..39011181ed357 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -15433,7 +15433,11 @@ TEST_F(FormatTest, AllowShortRecordOnASingleLine) {
"{ int i; };",
Style);
Style.AllowShortRecordOnASingleLine = FormatStyle::SRS_Always;
- verifyFormat("class foo { int i; };", Style);
+ verifyFormat("class foo\n"
+ "{\n"
+ "};\n"
+ "class foo { int i; };",
+ Style);
}
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
>From 8f0b1ef6ef6df14eab83f53140dcc9f35ae3c102 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sat, 20 Sep 2025 03:36:53 +0200
Subject: [PATCH 23/25] Fix linux build failing
---
clang/lib/Format/TokenAnnotator.cpp | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 9458c396679e0..1efbff1e262ce 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5981,11 +5981,12 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
// FIXME: Not covered by tests.
if (Right.isNot(TT_FunctionLBrace)) {
return Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Never &&
- (Line.startsWith(tok::kw_class) &&
- Style.BraceWrapping.AfterClass) ||
- (Line.startsWith(tok::kw_struct) &&
- Style.BraceWrapping.AfterStruct) ||
- (Line.startsWith(tok::kw_union) && Style.BraceWrapping.AfterUnion);
+ ((Line.startsWith(tok::kw_class) &&
+ Style.BraceWrapping.AfterClass) ||
+ (Line.startsWith(tok::kw_struct) &&
+ Style.BraceWrapping.AfterStruct) ||
+ (Line.startsWith(tok::kw_union) &&
+ Style.BraceWrapping.AfterUnion));
}
}
>From 675426ac0015aa3d708f0ae14402bbd693334d8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Sun, 21 Sep 2025 13:49:33 +0200
Subject: [PATCH 24/25] Comment fixups
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 56 +++++++++------------
1 file changed, 24 insertions(+), 32 deletions(-)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index c3dde162e2671..ce8408384565e 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,6 +10,7 @@
#include "FormatToken.h"
#include "NamespaceEndCommentsFixer.h"
#include "WhitespaceManager.h"
+#include "clang/Basic/TokenKinds.h"
#include "llvm/Support/Debug.h"
#include <queue>
@@ -586,35 +587,34 @@ class LineJoiner {
const auto *Line = I[0];
const auto *NextLine = I[1];
- auto GetRelevantAfterOption = [&](const FormatToken *Tok) {
- switch (Tok->getType()) {
- case TT_StructLBrace:
- return Style.BraceWrapping.AfterStruct;
- case TT_ClassLBrace:
- return Style.BraceWrapping.AfterClass;
- case TT_UnionLBrace:
- return Style.BraceWrapping.AfterUnion;
- default:
- return false;
- };
- };
-
// Current line begins both record and block, brace was not wrapped.
if (Line->Last->isOneOf(TT_StructLBrace, TT_ClassLBrace, TT_UnionLBrace)) {
+ auto ShouldWrapLBrace = [&](TokenType LBraceType) {
+ switch (LBraceType) {
+ case TT_StructLBrace:
+ return Style.BraceWrapping.AfterStruct;
+ case TT_ClassLBrace:
+ return Style.BraceWrapping.AfterClass;
+ case TT_UnionLBrace:
+ return Style.BraceWrapping.AfterUnion;
+ default:
+ return false;
+ };
+ };
+
auto TryMergeShortRecord = [&] {
switch (Style.AllowShortRecordOnASingleLine) {
case FormatStyle::SRS_Never:
return false;
- case FormatStyle::SRS_EmptyIfAttached:
- case FormatStyle::SRS_Empty:
- return NextLine->First->is(tok::r_brace);
case FormatStyle::SRS_Always:
return true;
+ default:
+ return NextLine->First->is(tok::r_brace);
}
};
if (Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Never &&
- (!GetRelevantAfterOption(Line->Last) ||
+ (!ShouldWrapLBrace(Line->Last->getType()) ||
(!Style.BraceWrapping.SplitEmptyRecord && TryMergeShortRecord()))) {
return tryMergeSimpleBlock(I, E, Limit);
}
@@ -622,27 +622,19 @@ class LineJoiner {
// Cases where the l_brace was wrapped.
// Current line begins record, next line block.
- if (NextLine->First->isOneOf(TT_StructLBrace, TT_ClassLBrace,
+ if (NextLine->First->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace)) {
- if (I + 2 == E)
+ if (I + 2 == E || I[2]->First->is(tok::r_brace) ||
+ Style.AllowShortRecordOnASingleLine != FormatStyle::SRS_Always) {
return 0;
-
- const bool IsEmptyBlock = I[2]->First->is(tok::r_brace);
-
- if (!IsEmptyBlock &&
- Style.AllowShortRecordOnASingleLine == FormatStyle::SRS_Always) {
- return tryMergeSimpleBlock(I, E, Limit);
}
- }
-
- if (I == AnnotatedLines.begin())
- return 0;
- const auto *PreviousLine = I[-1];
+ return tryMergeSimpleBlock(I, E, Limit);
+ }
// Previous line begins record, current line block.
- if (PreviousLine->First->isOneOf(tok::kw_struct, tok::kw_class,
- tok::kw_union)) {
+ if (I != AnnotatedLines.begin() &&
+ I[-1]->First->isOneOf(tok::kw_struct, tok::kw_class, tok::kw_union)) {
const bool IsEmptyBlock =
Line->Last->is(tok::l_brace) && NextLine->First->is(tok::r_brace);
>From 014700f50098f33248c851ea8fc43d2cb19bcf45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Slanina?= <itzexpoexpo at gmail.com>
Date: Fri, 28 Nov 2025 02:34:45 +0100
Subject: [PATCH 25/25] Fix handling of Java/JS records and interfaces
---
clang/lib/Format/UnwrappedLineFormatter.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index ce8408384565e..3c6aac7e335d8 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -512,6 +512,13 @@ class LineJoiner {
} else if (TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace,
TT_UnionLBrace)) {
return tryMergeRecord(I, E, Limit);
+ } else if (TheLine->Last->is(TT_RecordLBrace)) {
+ // NOTE: We use AfterClass (whereas AfterStruct exists) for both classes
+ // and structs, but it seems that wrapping is still handled correctly
+ // elsewhere.
+ ShouldMerge = !Style.BraceWrapping.AfterClass ||
+ (NextLine.First->is(tok::r_brace) &&
+ !Style.BraceWrapping.SplitEmptyRecord);
} else if (TheLine->InPPDirective ||
TheLine->First->isNoneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct, Keywords.kw_record)) {
More information about the cfe-commits
mailing list