-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[clang-format] Add option AllowShortRecordOnASingleLine #154580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit supersedes PR llvm#151970 by adding the option AllowShortRecordsOnASingleLine that allows the following formatting: struct foo {}; struct bar { int i; }; struct baz { int i; int j; int k; };
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-clang @llvm/pr-subscribers-clang-format Author: Tomáš Slanina (itzexpoexpo) ChangesThis patch supersedes PR #151970 by adding the option struct foo {};
struct bar { int i; };
struct baz
{
int i;
int j;
int k;
}; Full diff: https://github.com/llvm/llvm-project/pull/154580.diff 8 Files Affected:
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 02986a94a656c..537d81747a834 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -2093,6 +2093,38 @@ the configuration (without a prefix: ``Auto``).
**AllowShortNamespacesOnASingleLine** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <AllowShortNamespacesOnASingleLine>`
If ``true``, ``namespace a { class b; }`` can be put on a single line.
+.. _AllowShortRecordsOnASingleLine:
+
+**AllowShortRecordsOnASingleLine** (``ShortRecordStyle``) :ref:`¶ <AllowShortRecordsOnASingleLine>`
+ Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ line.
+
+ Possible values:
+
+ * ``SRS_Never`` (in configuration: ``Never``)
+ Never merge records into a single line.
+
+ * ``SRS_Empty`` (in configuration: ``Empty``)
+ Only merge empty records.
+
+ .. code-block:: c++
+
+ struct foo {};
+ struct bar
+ {
+ int i;
+ };
+
+ * ``SRS_All`` (in configuration: ``All``)
+ Merge all records that fit on a single line.
+
+ .. code-block:: c++
+
+ struct foo {};
+ struct bar { int i; };
+
+
+
.. _AlwaysBreakAfterDefinitionReturnType:
**AlwaysBreakAfterDefinitionReturnType** (``DefinitionReturnTypeBreakingStyle``) :versionbadge:`clang-format 3.7` :ref:`¶ <AlwaysBreakAfterDefinitionReturnType>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 31582a40de866..27f4e7cd9b6a7 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -992,6 +992,32 @@ struct FormatStyle {
/// \version 20
bool AllowShortNamespacesOnASingleLine;
+ /// Different styles for merging short records
+ /// (``class``,``struct``,``union``).
+ enum ShortRecordStyle : int8_t {
+ /// Never merge records into a single line.
+ SRS_Never,
+ /// Only merge empty records.
+ /// \code
+ /// struct foo {};
+ /// struct bar
+ /// {
+ /// int i;
+ /// };
+ /// \endcode
+ SRS_Empty,
+ /// Merge all records that fit on a single line.
+ /// \code
+ /// struct foo {};
+ /// struct bar { int i; };
+ /// \endcode
+ SRS_All
+ };
+
+ /// Dependent on the value, ``struct bar { int i; }`` can be put on a single
+ /// line.
+ ShortRecordStyle AllowShortRecordsOnASingleLine;
+
/// Different ways to break after the function definition return type.
/// This option is **deprecated** and is retained for backwards compatibility.
enum DefinitionReturnTypeBreakingStyle : int8_t {
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 063780721423f..4f9c0dfcad722 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -660,6 +660,14 @@ template <> struct ScalarEnumerationTraits<FormatStyle::ShortLambdaStyle> {
}
};
+template <> struct ScalarEnumerationTraits<FormatStyle::ShortRecordStyle> {
+ static void enumeration(IO &IO, FormatStyle::ShortRecordStyle &Value) {
+ IO.enumCase(Value, "Never", FormatStyle::SRS_Never);
+ IO.enumCase(Value, "Empty", FormatStyle::SRS_Empty);
+ IO.enumCase(Value, "All", FormatStyle::SRS_All);
+ }
+};
+
template <> struct MappingTraits<FormatStyle::SortIncludesOptions> {
static void enumInput(IO &IO, FormatStyle::SortIncludesOptions &Value) {
IO.enumCase(Value, "Never", FormatStyle::SortIncludesOptions({}));
@@ -1012,6 +1020,8 @@ template <> struct MappingTraits<FormatStyle> {
Style.AllowShortIfStatementsOnASingleLine);
IO.mapOptional("AllowShortLambdasOnASingleLine",
Style.AllowShortLambdasOnASingleLine);
+ IO.mapOptional("AllowShortRecordsOnASingleLine",
+ Style.AllowShortRecordsOnASingleLine);
IO.mapOptional("AllowShortLoopsOnASingleLine",
Style.AllowShortLoopsOnASingleLine);
IO.mapOptional("AllowShortNamespacesOnASingleLine",
@@ -1535,6 +1545,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
LLVMStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
LLVMStyle.AllowShortLambdasOnASingleLine = FormatStyle::SLS_All;
+ LLVMStyle.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
LLVMStyle.AllowShortLoopsOnASingleLine = false;
LLVMStyle.AllowShortNamespacesOnASingleLine = false;
LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None;
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 4801d27b1395a..50ed77313366a 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -5933,12 +5933,15 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
return true;
}
- // Don't attempt to interpret struct return types as structs.
+ // Don't attempt to interpret record return types as records.
if (Right.isNot(TT_FunctionLBrace)) {
- return (Line.startsWith(tok::kw_class) &&
- Style.BraceWrapping.AfterClass) ||
- (Line.startsWith(tok::kw_struct) &&
- Style.BraceWrapping.AfterStruct);
+ return ((Line.startsWith(tok::kw_class) &&
+ Style.BraceWrapping.AfterClass) ||
+ (Line.startsWith(tok::kw_struct) &&
+ Style.BraceWrapping.AfterStruct) ||
+ (Line.startsWith(tok::kw_union) &&
+ Style.BraceWrapping.AfterUnion)) &&
+ Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never;
}
}
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 0adf7ee9ed543..f07559ab96418 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -453,6 +453,19 @@ class LineJoiner {
}
}
+ auto ShouldMergeShortRecords = [this, &I, &NextLine, PreviousLine,
+ TheLine]() {
+ if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_All)
+ return true;
+ if (Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Empty &&
+ NextLine.First->is(tok::r_brace)) {
+ return true;
+ }
+ return false;
+ };
+
+ bool MergeShortRecord = ShouldMergeShortRecords();
+
// Don't merge an empty template class or struct if SplitEmptyRecords
// is defined.
if (PreviousLine && Style.BraceWrapping.SplitEmptyRecord &&
@@ -495,7 +508,8 @@ class LineJoiner {
// elsewhere.
ShouldMerge = !Style.BraceWrapping.AfterClass ||
(NextLine.First->is(tok::r_brace) &&
- !Style.BraceWrapping.SplitEmptyRecord);
+ !Style.BraceWrapping.SplitEmptyRecord) ||
+ MergeShortRecord;
} else if (TheLine->InPPDirective ||
!TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
tok::kw_struct)) {
@@ -869,9 +883,11 @@ class LineJoiner {
return 1;
} else if (Limit != 0 && !Line.startsWithNamespace() &&
!startsExternCBlock(Line)) {
- // We don't merge short records.
- if (isRecordLBrace(*Line.Last))
+ // Merge short records only when requested.
+ if (isRecordLBrace(*Line.Last) &&
+ Style.AllowShortRecordsOnASingleLine == FormatStyle::SRS_Never) {
return 0;
+ }
// Check that we still have three lines and they fit into the limit.
if (I + 2 == E || I[2]->Type == LT_Invalid)
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 91b8fdc8a3c38..3b23502febede 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -952,20 +952,26 @@ static bool isIIFE(const UnwrappedLine &Line,
}
static bool ShouldBreakBeforeBrace(const FormatStyle &Style,
- const FormatToken &InitialToken) {
+ const FormatToken &InitialToken,
+ const FormatToken &NextToken) {
tok::TokenKind Kind = InitialToken.Tok.getKind();
if (InitialToken.is(TT_NamespaceMacro))
Kind = tok::kw_namespace;
+ bool IsEmptyBlock = NextToken.is(tok::r_brace);
+ bool WrapRecordAllowed =
+ !(IsEmptyBlock &&
+ Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_Never);
+
switch (Kind) {
case tok::kw_namespace:
return Style.BraceWrapping.AfterNamespace;
case tok::kw_class:
- return Style.BraceWrapping.AfterClass;
+ return Style.BraceWrapping.AfterClass && WrapRecordAllowed;
case tok::kw_union:
- return Style.BraceWrapping.AfterUnion;
+ return Style.BraceWrapping.AfterUnion && WrapRecordAllowed;
case tok::kw_struct:
- return Style.BraceWrapping.AfterStruct;
+ return Style.BraceWrapping.AfterStruct && WrapRecordAllowed;
case tok::kw_enum:
return Style.BraceWrapping.AfterEnum;
default:
@@ -3191,7 +3197,7 @@ void UnwrappedLineParser::parseNamespace() {
if (FormatTok->is(tok::l_brace)) {
FormatTok->setFinalizedType(TT_NamespaceLBrace);
- if (ShouldBreakBeforeBrace(Style, InitialToken))
+ if (ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken()))
addUnwrappedLine();
unsigned AddLevels =
@@ -3856,7 +3862,7 @@ bool UnwrappedLineParser::parseEnum() {
}
if (!Style.AllowShortEnumsOnASingleLine &&
- ShouldBreakBeforeBrace(Style, InitialToken)) {
+ ShouldBreakBeforeBrace(Style, InitialToken, *Tokens->peekNextToken())) {
addUnwrappedLine();
}
// Parse enum body.
@@ -4151,8 +4157,11 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
if (ParseAsExpr) {
parseChildBlock();
} else {
- if (ShouldBreakBeforeBrace(Style, InitialToken))
+ if (Style.AllowShortRecordsOnASingleLine != FormatStyle::SRS_All &&
+ ShouldBreakBeforeBrace(Style, InitialToken,
+ *Tokens->peekNextToken())) {
addUnwrappedLine();
+ }
unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 9de3cca71630d..338f10f8ff4b9 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -655,6 +655,14 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("AllowShortLambdasOnASingleLine: true",
AllowShortLambdasOnASingleLine, FormatStyle::SLS_All);
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: Empty",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_Empty);
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: All",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_All);
+ CHECK_PARSE("AllowShortRecordsOnASingleLine: Never",
+ AllowShortRecordsOnASingleLine, FormatStyle::SRS_Never);
+
Style.SpaceAroundPointerQualifiers = FormatStyle::SAPQ_Both;
CHECK_PARSE("SpaceAroundPointerQualifiers: Default",
SpaceAroundPointerQualifiers, FormatStyle::SAPQ_Default);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 96cc650f52a5d..e62a37a772cbd 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8624,6 +8624,19 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) {
Style);
}
+TEST_F(FormatTest, BreakFunctionsReturningRecords) {
+ FormatStyle Style = getLLVMStyle();
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterFunction = true;
+ Style.BraceWrapping.AfterClass = false;
+ Style.BraceWrapping.AfterStruct = false;
+ Style.BraceWrapping.AfterUnion = false;
+
+ verifyFormat("class Bar foo() {}", Style);
+ verifyFormat("struct Bar foo() {}", Style);
+ verifyFormat("union Bar foo() {}", Style);
+}
+
TEST_F(FormatTest, DontBreakBeforeQualifiedOperator) {
// Regression test for https://bugs.llvm.org/show_bug.cgi?id=40516:
// Prefer keeping `::` followed by `operator` together.
@@ -15615,6 +15628,66 @@ TEST_F(FormatTest, NeverMergeShortRecords) {
Style);
}
+TEST_F(FormatTest, AllowShortRecordsOnASingleLine) {
+ FormatStyle Style = getLLVMStyle();
+
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterClass = true;
+ Style.BraceWrapping.AfterStruct = true;
+ Style.BraceWrapping.AfterUnion = true;
+ Style.BraceWrapping.SplitEmptyRecord = false;
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Never;
+
+ verifyFormat("class foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo\n{};", Style);
+
+ verifyFormat("struct foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo\n{};", Style);
+
+ verifyFormat("union foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo\n{};", Style);
+
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_Empty;
+
+ verifyFormat("class foo\n{\n"
+ " void bar();\n"
+ "};",
+ Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo\n{\n"
+ " int bar;\n"
+ "};",
+ Style);
+ verifyFormat("union foo {};", Style);
+
+ Style.AllowShortRecordsOnASingleLine = FormatStyle::SRS_All;
+
+ verifyFormat("class foo { void bar(); };", Style);
+ verifyFormat("class foo {};", Style);
+
+ verifyFormat("struct foo { int bar; };", Style);
+ verifyFormat("struct foo {};", Style);
+
+ verifyFormat("union foo { int bar; };", Style);
+ verifyFormat("union foo {};", Style);
+}
+
TEST_F(FormatTest, UnderstandContextOfRecordTypeKeywords) {
// Elaborate type variable declarations.
verifyFormat("struct foo a = {bar};\nint n;");
|
Looks like we got an inconsistency in formatting short (including empty) records. With 20.1.8 we have:
Have you investigated why? It may be worthwhile to try fixing it first. |
Could you please provide the used config too? Might be necessary to add some more test cases around this I tested on the default configuration and I get the same output on both 20.1.0 and the latest commit, so I'm unsure where the inconsistency is? Using BraceWrapping to highlight new option changes: $ clang-format --version
clang-format version 20.1.0
$ clang-format a.cpp
struct foo {};
struct bar {
int i;
};
$ clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}}"
struct foo
{
};
struct bar
{
int i;
};
$ bin\clang-format --version
clang-format version 22.0.0git (https://github.com/itzexpoexpo/llvm-project-wrapemptyrecord.git 6659057cfc4ecebc8ed963d57cb158b3b34d209e)
$ bin\clang-format a.cpp
struct foo {};
struct bar {
int i;
};
$ bin\clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}}"
struct foo
{
};
struct bar
{
int i;
};
$ bin\clang-format a.cpp --style="{BreakBeforeBraces: Custom, BraceWrapping: {AfterStruct: true}, AllowShortRecordsOnASingleLine: All }"
struct foo {};
struct bar { int i; }; |
It's the default config. No I've just glanced at the code WRT merging short/empty records ( |
Right, I see what you mean now, it also ignores explicitly specified |
With this patch:
Expected:
class foo {};
class bar {
int i;
};
class foo {
};
class bar {
int i;
}; Am I correct? |
Yes, except
Both are currently correct unless the handling of |
The command-line option
|
The same code that merges the brace for struct foo {
}; from your comment I understood this behavior is not to be changed?
Right, so is the expected behavior this: $ clang-format -style="{AllowShortRecordsOnASingleLine: Empty, BraceWrapping: {AfterClass: true}, BreakBeforeBraces: Custom}" a.cpp
struct foo
{
}; i.e. should |
See https://github.com/llvm/llvm-project/pull/154580/files#r2296871961. The default (i.e. the LLVM style) of
Yep! |
See #151428. Maybe there's a similar bug with records. |
What I have changed is something like
Would this code have any bug you guys have mentioned before? |
This patch supersedes PR #151970 by adding the option
AllowShortRecordOnASingleLine
that allows the following formatting: